diff --git a/framework/src/android/net/CaptivePortal.java b/framework/src/android/net/CaptivePortal.java new file mode 100644 index 0000000000..269bbf20c8 --- /dev/null +++ b/framework/src/android/net/CaptivePortal.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed urnder 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 android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +/** + * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN} + * activity to indicate to the system different outcomes of captive portal sign in. This class is + * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the + * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity. + */ +public class CaptivePortal implements Parcelable { + /** + * Response code from the captive portal application, indicating that the portal was dismissed + * and the network should be re-validated. + * @see ICaptivePortal#appResponse(int) + * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int) + * @hide + */ + @SystemApi + public static final int APP_RETURN_DISMISSED = 0; + /** + * Response code from the captive portal application, indicating that the user did not login and + * does not want to use the captive portal network. + * @see ICaptivePortal#appResponse(int) + * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int) + * @hide + */ + @SystemApi + public static final int APP_RETURN_UNWANTED = 1; + /** + * Response code from the captive portal application, indicating that the user does not wish to + * login but wants to use the captive portal network as-is. + * @see ICaptivePortal#appResponse(int) + * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int) + * @hide + */ + @SystemApi + public static final int APP_RETURN_WANTED_AS_IS = 2; + /** Event offset of request codes from captive portal application. */ + private static final int APP_REQUEST_BASE = 100; + /** + * Request code from the captive portal application, indicating that the network condition may + * have changed and the network should be re-validated. + * @see ICaptivePortal#appRequest(int) + * @see android.net.INetworkMonitor#forceReevaluation(int) + * @hide + */ + @SystemApi + public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0; + + private final IBinder mBinder; + + /** @hide */ + public CaptivePortal(@NonNull IBinder binder) { + mBinder = binder; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeStrongBinder(mBinder); + } + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public CaptivePortal createFromParcel(Parcel in) { + return new CaptivePortal(in.readStrongBinder()); + } + + @Override + public CaptivePortal[] newArray(int size) { + return new CaptivePortal[size]; + } + }; + + /** + * Indicate to the system that the captive portal has been + * dismissed. In response the framework will re-evaluate the network's + * connectivity and might take further action thereafter. + */ + public void reportCaptivePortalDismissed() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED); + } catch (RemoteException e) { + } + } + + /** + * Indicate to the system that the user does not want to pursue signing in to the + * captive portal and the system should continue to prefer other networks + * without captive portals for use as the default active data network. The + * system will not retest the network for a captive portal so as to avoid + * disturbing the user with further sign in to network notifications. + */ + public void ignoreNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED); + } catch (RemoteException e) { + } + } + + /** + * Indicate to the system the user wants to use this network as is, even though + * the captive portal is still in place. The system will treat the network + * as if it did not have a captive portal when selecting the network to use + * as the default active data network. This may result in this network + * becoming the default active data network, which could disrupt network + * connectivity for apps because the captive portal is still in place. + * @hide + */ + @SystemApi + public void useNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS); + } catch (RemoteException e) { + } + } + + /** + * Request that the system reevaluates the captive portal status. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void reevaluateNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED); + } catch (RemoteException e) { + } + } + + /** + * Log a captive portal login event. + * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. + * @param packageName captive portal application package name. + * @hide + */ + @SystemApi + public void logEvent(int eventId, @NonNull String packageName) { + try { + ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName); + } catch (RemoteException e) { + } + } +} diff --git a/framework/src/android/net/CaptivePortalData.aidl b/framework/src/android/net/CaptivePortalData.aidl new file mode 100644 index 0000000000..1d57ee7591 --- /dev/null +++ b/framework/src/android/net/CaptivePortalData.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +@JavaOnlyStableParcelable parcelable CaptivePortalData; diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java new file mode 100644 index 0000000000..9b56b23cc8 --- /dev/null +++ b/framework/src/android/net/CaptivePortalData.java @@ -0,0 +1,305 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Metadata sent by captive portals, see https://www.ietf.org/id/draft-ietf-capport-api-03.txt. + * @hide + */ +@SystemApi +public final class CaptivePortalData implements Parcelable { + private final long mRefreshTimeMillis; + @Nullable + private final Uri mUserPortalUrl; + @Nullable + private final Uri mVenueInfoUrl; + private final boolean mIsSessionExtendable; + private final long mByteLimit; + private final long mExpiryTimeMillis; + private final boolean mCaptive; + private final String mVenueFriendlyName; + + private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, + boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, + String venueFriendlyName) { + mRefreshTimeMillis = refreshTimeMillis; + mUserPortalUrl = userPortalUrl; + mVenueInfoUrl = venueInfoUrl; + mIsSessionExtendable = isSessionExtendable; + mByteLimit = byteLimit; + mExpiryTimeMillis = expiryTimeMillis; + mCaptive = captive; + mVenueFriendlyName = venueFriendlyName; + } + + private CaptivePortalData(Parcel p) { + this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), + p.readLong(), p.readLong(), p.readBoolean(), p.readString()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mRefreshTimeMillis); + dest.writeParcelable(mUserPortalUrl, 0); + dest.writeParcelable(mVenueInfoUrl, 0); + dest.writeBoolean(mIsSessionExtendable); + dest.writeLong(mByteLimit); + dest.writeLong(mExpiryTimeMillis); + dest.writeBoolean(mCaptive); + dest.writeString(mVenueFriendlyName); + } + + /** + * A builder to create new {@link CaptivePortalData}. + */ + public static class Builder { + private long mRefreshTime; + private Uri mUserPortalUrl; + private Uri mVenueInfoUrl; + private boolean mIsSessionExtendable; + private long mBytesRemaining = -1; + private long mExpiryTime = -1; + private boolean mCaptive; + private String mVenueFriendlyName; + + /** + * Create an empty builder. + */ + public Builder() {} + + /** + * Create a builder copying all data from existing {@link CaptivePortalData}. + */ + public Builder(@Nullable CaptivePortalData data) { + if (data == null) return; + setRefreshTime(data.mRefreshTimeMillis) + .setUserPortalUrl(data.mUserPortalUrl) + .setVenueInfoUrl(data.mVenueInfoUrl) + .setSessionExtendable(data.mIsSessionExtendable) + .setBytesRemaining(data.mByteLimit) + .setExpiryTime(data.mExpiryTimeMillis) + .setCaptive(data.mCaptive) + .setVenueFriendlyName(data.mVenueFriendlyName); + } + + /** + * Set the time at which data was last refreshed, as per {@link System#currentTimeMillis()}. + */ + @NonNull + public Builder setRefreshTime(long refreshTime) { + mRefreshTime = refreshTime; + return this; + } + + /** + * Set the URL to be used for users to login to the portal, if captive. + */ + @NonNull + public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) { + mUserPortalUrl = userPortalUrl; + return this; + } + + /** + * Set the URL that can be used by users to view information about the network venue. + */ + @NonNull + public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) { + mVenueInfoUrl = venueInfoUrl; + return this; + } + + /** + * Set whether the portal supports extending a user session on the portal URL page. + */ + @NonNull + public Builder setSessionExtendable(boolean sessionExtendable) { + mIsSessionExtendable = sessionExtendable; + return this; + } + + /** + * Set the number of bytes remaining on the network before the portal closes. + */ + @NonNull + public Builder setBytesRemaining(long bytesRemaining) { + mBytesRemaining = bytesRemaining; + return this; + } + + /** + * Set the time at the session will expire, as per {@link System#currentTimeMillis()}. + */ + @NonNull + public Builder setExpiryTime(long expiryTime) { + mExpiryTime = expiryTime; + return this; + } + + /** + * Set whether the network is captive (portal closed). + */ + @NonNull + public Builder setCaptive(boolean captive) { + mCaptive = captive; + return this; + } + + /** + * Set the venue friendly name. + */ + @NonNull + public Builder setVenueFriendlyName(@Nullable String venueFriendlyName) { + mVenueFriendlyName = venueFriendlyName; + return this; + } + + /** + * Create a new {@link CaptivePortalData}. + */ + @NonNull + public CaptivePortalData build() { + return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl, + mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive, + mVenueFriendlyName); + } + } + + /** + * Get the time at which data was last refreshed, as per {@link System#currentTimeMillis()}. + */ + public long getRefreshTimeMillis() { + return mRefreshTimeMillis; + } + + /** + * Get the URL to be used for users to login to the portal, or extend their session if + * {@link #isSessionExtendable()} is true. + */ + @Nullable + public Uri getUserPortalUrl() { + return mUserPortalUrl; + } + + /** + * Get the URL that can be used by users to view information about the network venue. + */ + @Nullable + public Uri getVenueInfoUrl() { + return mVenueInfoUrl; + } + + /** + * Indicates whether the user portal URL can be used to extend sessions, when the user is logged + * in and the session has a time or byte limit. + */ + public boolean isSessionExtendable() { + return mIsSessionExtendable; + } + + /** + * Get the remaining bytes on the captive portal session, at the time {@link CaptivePortalData} + * was refreshed. This may be different from the limit currently enforced by the portal. + * @return The byte limit, or -1 if not set. + */ + public long getByteLimit() { + return mByteLimit; + } + + /** + * Get the time at the session will expire, as per {@link System#currentTimeMillis()}. + * @return The expiry time, or -1 if unset. + */ + public long getExpiryTimeMillis() { + return mExpiryTimeMillis; + } + + /** + * Get whether the network is captive (portal closed). + */ + public boolean isCaptive() { + return mCaptive; + } + + /** + * Get the venue friendly name + */ + @Nullable + public String getVenueFriendlyName() { + return mVenueFriendlyName; + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @Override + public CaptivePortalData createFromParcel(Parcel source) { + return new CaptivePortalData(source); + } + + @Override + public CaptivePortalData[] newArray(int size) { + return new CaptivePortalData[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, + mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof CaptivePortalData)) return false; + final CaptivePortalData other = (CaptivePortalData) obj; + return mRefreshTimeMillis == other.mRefreshTimeMillis + && Objects.equals(mUserPortalUrl, other.mUserPortalUrl) + && Objects.equals(mVenueInfoUrl, other.mVenueInfoUrl) + && mIsSessionExtendable == other.mIsSessionExtendable + && mByteLimit == other.mByteLimit + && mExpiryTimeMillis == other.mExpiryTimeMillis + && mCaptive == other.mCaptive + && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName); + } + + @Override + public String toString() { + return "CaptivePortalData {" + + "refreshTime: " + mRefreshTimeMillis + + ", userPortalUrl: " + mUserPortalUrl + + ", venueInfoUrl: " + mVenueInfoUrl + + ", isSessionExtendable: " + mIsSessionExtendable + + ", byteLimit: " + mByteLimit + + ", expiryTime: " + mExpiryTimeMillis + + ", captive: " + mCaptive + + ", venueFriendlyName: " + mVenueFriendlyName + + "}"; + } +} diff --git a/framework/src/android/net/ConnectionInfo.aidl b/framework/src/android/net/ConnectionInfo.aidl new file mode 100644 index 0000000000..07faf8bbbe --- /dev/null +++ b/framework/src/android/net/ConnectionInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright (C) 2018 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; + +parcelable ConnectionInfo; diff --git a/framework/src/android/net/ConnectionInfo.java b/framework/src/android/net/ConnectionInfo.java new file mode 100644 index 0000000000..4514a8484d --- /dev/null +++ b/framework/src/android/net/ConnectionInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 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 android.os.Parcel; +import android.os.Parcelable; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +/** + * Describe a network connection including local and remote address/port of a connection and the + * transport protocol. + * + * @hide + */ +public final class ConnectionInfo implements Parcelable { + public final int protocol; + public final InetSocketAddress local; + public final InetSocketAddress remote; + + @Override + public int describeContents() { + return 0; + } + + public ConnectionInfo(int protocol, InetSocketAddress local, InetSocketAddress remote) { + this.protocol = protocol; + this.local = local; + this.remote = remote; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(protocol); + out.writeByteArray(local.getAddress().getAddress()); + out.writeInt(local.getPort()); + out.writeByteArray(remote.getAddress().getAddress()); + out.writeInt(remote.getPort()); + } + + public static final @android.annotation.NonNull Creator CREATOR = new Creator() { + public ConnectionInfo createFromParcel(Parcel in) { + int protocol = in.readInt(); + InetAddress localAddress; + try { + localAddress = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Invalid InetAddress"); + } + int localPort = in.readInt(); + InetAddress remoteAddress; + try { + remoteAddress = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Invalid InetAddress"); + } + int remotePort = in.readInt(); + InetSocketAddress local = new InetSocketAddress(localAddress, localPort); + InetSocketAddress remote = new InetSocketAddress(remoteAddress, remotePort); + return new ConnectionInfo(protocol, local, remote); + } + + public ConnectionInfo[] newArray(int size) { + return new ConnectionInfo[size]; + } + }; +} diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.aidl b/framework/src/android/net/ConnectivityDiagnosticsManager.aidl new file mode 100644 index 0000000000..82ba0ca113 --- /dev/null +++ b/framework/src/android/net/ConnectivityDiagnosticsManager.aidl @@ -0,0 +1,21 @@ +/** + * + * Copyright (C) 2020 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; + +parcelable ConnectivityDiagnosticsManager.ConnectivityReport; +parcelable ConnectivityDiagnosticsManager.DataStallReport; \ No newline at end of file diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.java b/framework/src/android/net/ConnectivityDiagnosticsManager.java new file mode 100644 index 0000000000..5234494973 --- /dev/null +++ b/framework/src/android/net/ConnectivityDiagnosticsManager.java @@ -0,0 +1,777 @@ +/* + * 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 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; +import android.os.RemoteException; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +/** + * Class that provides utilities for collecting network connectivity diagnostics information. + * Connectivity information is made available through triggerable diagnostics tools and by listening + * to System validations. Some diagnostics information may be permissions-restricted. + * + *

ConnectivityDiagnosticsManager is intended for use by applications offering network + * connectivity on a user device. These tools will provide several mechanisms for these applications + * to be alerted to network conditions as well as diagnose potential network issues themselves. + * + *

The primary responsibilities of this class are to: + * + *

    + *
  • Allow permissioned applications to register and unregister callbacks for network event + * notifications + *
  • Invoke callbacks for network event notifications, including: + *
      + *
    • Network validations + *
    • Data stalls + *
    • Connectivity reports from applications + *
    + *
+ */ +public class ConnectivityDiagnosticsManager { + /** @hide */ + @VisibleForTesting + public static final Map + sCallbacks = new ConcurrentHashMap<>(); + + private final Context mContext; + private final IConnectivityManager mService; + + /** @hide */ + public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + } + + /** @hide */ + @VisibleForTesting + public static boolean persistableBundleEquals( + @Nullable PersistableBundle a, @Nullable PersistableBundle b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (!Objects.equals(a.keySet(), b.keySet())) return false; + for (String key : a.keySet()) { + if (!Objects.equals(a.get(key), b.get(key))) return false; + } + return true; + } + + /** 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#NET_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 = + "networkProbesAttempted"; + + /** @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; + + /** + * The timestamp for the report. The timestamp is taken from {@link + * System#currentTimeMillis}. + */ + private final long mReportTimestamp; + + /** LinkProperties available on the Network at the reported timestamp */ + @NonNull private final LinkProperties mLinkProperties; + + /** NetworkCapabilities available on the Network at the reported timestamp */ + @NonNull private final NetworkCapabilities mNetworkCapabilities; + + /** PersistableBundle that may contain additional info about the report */ + @NonNull private final PersistableBundle mAdditionalInfo; + + /** + * Constructor for ConnectivityReport. + * + *

Apps should obtain instances through {@link + * ConnectivityDiagnosticsCallback#onConnectivityReportAvailable} instead of instantiating + * their own instances (unless for testing purposes). + * + * @param network The Network for which this ConnectivityReport applies + * @param reportTimestamp The timestamp for the report + * @param linkProperties The LinkProperties available on network at reportTimestamp + * @param networkCapabilities The NetworkCapabilities available on network at + * reportTimestamp + * @param additionalInfo A PersistableBundle that may contain additional info about the + * report + */ + public ConnectivityReport( + @NonNull Network network, + long reportTimestamp, + @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull PersistableBundle additionalInfo) { + mNetwork = network; + mReportTimestamp = reportTimestamp; + mLinkProperties = new LinkProperties(linkProperties); + mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); + mAdditionalInfo = additionalInfo; + } + + /** + * Returns the Network for this ConnectivityReport. + * + * @return The Network for which this ConnectivityReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the LinkProperties available when this report was taken. + * + * @return LinkProperties available on the Network at the reported timestamp + */ + @NonNull + public LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + /** + * Returns the NetworkCapabilities when this report was taken. + * + * @return NetworkCapabilities available on the Network at the reported timestamp + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * @return PersistableBundle that may contain additional info about the report + */ + @NonNull + public PersistableBundle getAdditionalInfo() { + return new PersistableBundle(mAdditionalInfo); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof ConnectivityReport)) return false; + final ConnectivityReport that = (ConnectivityReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mNetwork.equals(that.mNetwork) + && mLinkProperties.equals(that.mLinkProperties) + && mNetworkCapabilities.equals(that.mNetworkCapabilities) + && persistableBundleEquals(mAdditionalInfo, that.mAdditionalInfo); + } + + @Override + public int hashCode() { + return Objects.hash( + mNetwork, + mReportTimestamp, + mLinkProperties, + mNetworkCapabilities, + mAdditionalInfo); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeParcelable(mLinkProperties, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + dest.writeParcelable(mAdditionalInfo, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + public ConnectivityReport createFromParcel(Parcel in) { + return new ConnectivityReport( + in.readParcelable(null), + in.readLong(), + in.readParcelable(null), + in.readParcelable(null), + in.readParcelable(null)); + } + + public ConnectivityReport[] newArray(int size) { + return new ConnectivityReport[size]; + } + }; + } + + /** Class that includes information for a suspected data stall on a specific Network */ + public static final class DataStallReport implements Parcelable { + /** + * Indicates that the Data Stall was detected using DNS events. + */ + public static final int DETECTION_METHOD_DNS_EVENTS = 1; + + /** + * Indicates that the Data Stall was detected using TCP metrics. + */ + public static final int DETECTION_METHOD_TCP_METRICS = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"DETECTION_METHOD_"}, + 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; + + /** + * The timestamp for the report. The timestamp is taken from {@link + * System#currentTimeMillis}. + */ + private long mReportTimestamp; + + /** A bitmask of the detection methods used to identify the suspected data stall */ + @DetectionMethod private final int mDetectionMethod; + + /** LinkProperties available on the Network at the reported timestamp */ + @NonNull private final LinkProperties mLinkProperties; + + /** NetworkCapabilities available on the Network at the reported timestamp */ + @NonNull private final NetworkCapabilities mNetworkCapabilities; + + /** PersistableBundle that may contain additional information on the suspected data stall */ + @NonNull private final PersistableBundle mStallDetails; + + /** + * Constructor for DataStallReport. + * + *

Apps should obtain instances through {@link + * ConnectivityDiagnosticsCallback#onDataStallSuspected} instead of instantiating their own + * instances (unless for testing purposes). + * + * @param network The Network for which this DataStallReport applies + * @param reportTimestamp The timestamp for the report + * @param detectionMethod The detection method used to identify this data stall + * @param linkProperties The LinkProperties available on network at reportTimestamp + * @param networkCapabilities The NetworkCapabilities available on network at + * reportTimestamp + * @param stallDetails A PersistableBundle that may contain additional info about the report + */ + public DataStallReport( + @NonNull Network network, + long reportTimestamp, + @DetectionMethod int detectionMethod, + @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull PersistableBundle stallDetails) { + mNetwork = network; + mReportTimestamp = reportTimestamp; + mDetectionMethod = detectionMethod; + mLinkProperties = new LinkProperties(linkProperties); + mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); + mStallDetails = stallDetails; + } + + /** + * Returns the Network for this DataStallReport. + * + * @return The Network for which this DataStallReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the bitmask of detection methods used to identify this suspected data stall. + * + * @return The bitmask of detection methods used to identify the suspected data stall + */ + public int getDetectionMethod() { + return mDetectionMethod; + } + + /** + * Returns the LinkProperties available when this report was taken. + * + * @return LinkProperties available on the Network at the reported timestamp + */ + @NonNull + public LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + /** + * Returns the NetworkCapabilities when this report was taken. + * + * @return NetworkCapabilities available on the Network at the reported timestamp + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + /** + * 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 + */ + @NonNull + public PersistableBundle getStallDetails() { + return new PersistableBundle(mStallDetails); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof DataStallReport)) return false; + final DataStallReport that = (DataStallReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mDetectionMethod == that.mDetectionMethod + && mNetwork.equals(that.mNetwork) + && mLinkProperties.equals(that.mLinkProperties) + && mNetworkCapabilities.equals(that.mNetworkCapabilities) + && persistableBundleEquals(mStallDetails, that.mStallDetails); + } + + @Override + public int hashCode() { + return Objects.hash( + mNetwork, + mReportTimestamp, + mDetectionMethod, + mLinkProperties, + mNetworkCapabilities, + mStallDetails); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeInt(mDetectionMethod); + dest.writeParcelable(mLinkProperties, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + dest.writeParcelable(mStallDetails, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + public DataStallReport createFromParcel(Parcel in) { + return new DataStallReport( + in.readParcelable(null), + in.readLong(), + in.readInt(), + in.readParcelable(null), + in.readParcelable(null), + in.readParcelable(null)); + } + + public DataStallReport[] newArray(int size) { + return new DataStallReport[size]; + } + }; + } + + /** @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 onConnectivityReportAvailable(@NonNull ConnectivityReport report) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCb.onConnectivityReportAvailable(report); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** @hide */ + @VisibleForTesting + public void onDataStallSuspected(@NonNull DataStallReport report) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCb.onDataStallSuspected(report); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** @hide */ + @VisibleForTesting + public void onNetworkConnectivityReported( + @NonNull Network network, boolean hasConnectivity) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCb.onNetworkConnectivityReported(network, hasConnectivity); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + /** + * Abstract base class for Connectivity Diagnostics callbacks. Used for notifications about + * network connectivity events. Must be extended by applications wanting notifications. + */ + public abstract static class ConnectivityDiagnosticsCallback { + /** + * Called when the platform completes a data connectivity check. This will also be invoked + * immediately upon registration for each network matching the request with the latest + * report, if a report has already been generated for that network. + * + *

The Network specified in the ConnectivityReport may not be active any more when this + * method is invoked. + * + * @param report The ConnectivityReport containing information about a connectivity check + */ + public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) {} + + /** + * Called when the platform suspects a data stall on some Network. + * + *

The Network specified in the DataStallReport may not be active any more when this + * method is invoked. + * + * @param report The DataStallReport containing information about the suspected data stall + */ + public void onDataStallSuspected(@NonNull DataStallReport report) {} + + /** + * Called when any app reports connectivity to the System. + * + * @param network The Network for which connectivity has been reported + * @param hasConnectivity The connectivity reported to the System + */ + public void onNetworkConnectivityReported( + @NonNull Network network, boolean hasConnectivity) {} + } + + /** + * Registers a ConnectivityDiagnosticsCallback with the System. + * + *

Only apps that offer network connectivity to the user should be registering callbacks. + * These are the only apps whose callbacks will be invoked by the system. Apps considered to + * meet these conditions include: + * + *

    + *
  • Carrier apps with active subscriptions + *
  • Active VPNs + *
  • WiFi Suggesters + *
+ * + *

Callbacks registered by apps not meeting the above criteria will not be invoked. + * + *

If a registering app loses its relevant permissions, any callbacks it registered will + * silently stop receiving callbacks. + * + *

Each register() call MUST use a ConnectivityDiagnosticsCallback instance that is + * not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with + * multiple NetworkRequests, an IllegalArgumentException will be thrown. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * callbacks in {@link ConnectivityManager}. Registering a callback with this method will count + * toward this limit. If this limit is exceeded, an exception will be thrown. To avoid hitting + * this issue and to conserve resources, make sure to unregister the callbacks with + * {@link #unregisterConnectivityDiagnosticsCallback}. + * + * @param request The NetworkRequest that will be used to match with Networks for which + * callbacks will be fired + * @param e The Executor to be used for running the callback method invocations + * @param callback The ConnectivityDiagnosticsCallback that the caller wants registered with the + * System + * @throws IllegalArgumentException if the same callback instance is registered with multiple + * NetworkRequests + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void registerConnectivityDiagnosticsCallback( + @NonNull NetworkRequest request, + @NonNull Executor e, + @NonNull ConnectivityDiagnosticsCallback callback) { + final ConnectivityDiagnosticsBinder binder = new ConnectivityDiagnosticsBinder(callback, e); + if (sCallbacks.putIfAbsent(callback, binder) != null) { + throw new IllegalArgumentException("Callback is currently registered"); + } + + try { + mService.registerConnectivityDiagnosticsCallback( + binder, request, mContext.getOpPackageName()); + } catch (RemoteException exception) { + exception.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a ConnectivityDiagnosticsCallback with the System. + * + *

If the given callback is not currently registered with the System, this operation will be + * a no-op. + * + * @param callback The ConnectivityDiagnosticsCallback to be unregistered from the System. + */ + public void unregisterConnectivityDiagnosticsCallback( + @NonNull ConnectivityDiagnosticsCallback callback) { + // unconditionally removing from sCallbacks prevents race conditions here, since remove() is + // atomic. + final ConnectivityDiagnosticsBinder binder = sCallbacks.remove(callback); + if (binder == null) return; + + try { + mService.unregisterConnectivityDiagnosticsCallback(binder); + } catch (RemoteException exception) { + exception.rethrowFromSystemServer(); + } + } +} diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java new file mode 100644 index 0000000000..ac8f9c9e62 --- /dev/null +++ b/framework/src/android/net/ConnectivityManager.java @@ -0,0 +1,5058 @@ +/* + * Copyright (C) 2008 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 android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; +import static android.net.NetworkRequest.Type.LISTEN; +import static android.net.NetworkRequest.Type.REQUEST; +import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.QosCallback.QosCallbackRegistrationException; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.PendingIntent; +import android.compat.annotation.UnsupportedAppUsage; +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; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.INetworkActivityListener; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.provider.Settings; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Range; +import android.util.SparseIntArray; + +import com.android.connectivity.aidl.INetworkAgent; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; +import com.android.internal.util.Protocol; + +import libcore.net.event.NetworkEventDispatcher; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; + +/** + * Class that answers queries about the state of network connectivity. It also + * notifies applications when network connectivity changes. + *

+ * The primary responsibilities of this class are to: + *

    + *
  1. Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)
  2. + *
  3. Send broadcast intents when network connectivity changes
  4. + *
  5. Attempt to "fail over" to another network when connectivity to a network + * is lost
  6. + *
  7. Provide an API that allows applications to query the coarse-grained or fine-grained + * state of the available networks
  8. + *
  9. Provide an API that allows applications to request and select networks for their data + * traffic
  10. + *
+ */ +@SystemService(Context.CONNECTIVITY_SERVICE) +public class ConnectivityManager { + private static final String TAG = "ConnectivityManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + /** + * A change in network connectivity has occurred. A default connection has either + * been established or lost. The NetworkInfo for the affected network is + * sent as an extra; it should be consulted to see what kind of + * connectivity event occurred. + *

+ * Apps targeting Android 7.0 (API level 24) and higher do not receive this + * broadcast if they declare the broadcast receiver in their manifest. Apps + * will still receive broadcasts if they register their + * {@link android.content.BroadcastReceiver} with + * {@link android.content.Context#registerReceiver Context.registerReceiver()} + * and that context is still valid. + *

+ * If this is a connection that was the result of failing over from a + * disconnected network, then the FAILOVER_CONNECTION boolean extra is + * set to true. + *

+ * For a loss of connectivity, if the connectivity manager is attempting + * to connect (or has already connected) to another network, the + * NetworkInfo for the new network is also passed as an extra. This lets + * any receivers of the broadcast know that they should not necessarily + * tell the user that no data traffic will be possible. Instead, the + * receiver should expect another broadcast soon, indicating either that + * the failover attempt succeeded (and so there is still overall data + * connectivity), or that the failover attempt failed, meaning that all + * connectivity has been lost. + *

+ * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY + * is set to {@code true} if there are no connected networks at all. + * + * @deprecated apps should use the more versatile {@link #requestNetwork}, + * {@link #registerNetworkCallback} or {@link #registerDefaultNetworkCallback} + * functions instead for faster and more detailed updates about the network + * changes they care about. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + + /** + * The device has connected to a network that has presented a captive + * portal, which is blocking Internet connectivity. The user was presented + * with a notification that network sign in is required, + * and the user invoked the notification's action indicating they + * desire to sign in to the network. Apps handling this activity should + * facilitate signing in to the network. This action includes a + * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents + * the network presenting the captive portal; all communication with the + * captive portal must be done using this {@code Network} object. + *

+ * This activity includes a {@link CaptivePortal} extra named + * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different + * outcomes of the captive portal sign in to the system: + *

    + *
  • When the app handling this action believes the user has signed in to + * the network and the captive portal has been dismissed, the app should + * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can + * reevaluate the network. If reevaluation finds the network no longer + * subject to a captive portal, the network may become the default active + * data network.
  • + *
  • When the app handling this action believes the user explicitly wants + * to ignore the captive portal and the network, the app should call + * {@link CaptivePortal#ignoreNetwork}.
  • + *
+ */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + + /** + * The lookup key for a {@link NetworkInfo} object. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * + * @deprecated The {@link NetworkInfo} object is deprecated, as many of its properties + * can't accurately represent modern network characteristics. + * Please obtain information about networks from the {@link NetworkCapabilities} + * or {@link LinkProperties} objects instead. + */ + @Deprecated + public static final String EXTRA_NETWORK_INFO = "networkInfo"; + + /** + * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast. + * + * @see android.content.Intent#getIntExtra(String, int) + * @deprecated The network type is not rich enough to represent the characteristics + * of modern networks. Please use {@link NetworkCapabilities} instead, + * in particular the transports. + */ + @Deprecated + public static final String EXTRA_NETWORK_TYPE = "networkType"; + + /** + * The lookup key for a boolean that indicates whether a connect event + * is for a network to which the connectivity manager was failing over + * following a disconnect on another network. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public static final String EXTRA_IS_FAILOVER = "isFailover"; + /** + * The lookup key for a {@link NetworkInfo} object. This is supplied when + * there is another network that it may be possible to connect to. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + /** + * The lookup key for a boolean that indicates whether there is a + * complete lack of connectivity, i.e., no network is available. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + /** + * The lookup key for a string that indicates why an attempt to connect + * to a network failed. The string has no particular structure. It is + * intended to be used in notifications presented to users. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_REASON = "reason"; + /** + * The lookup key for a string that provides optionally supplied + * extra information about the network state. The information + * may be passed up from the lower networking layers, and its + * meaning may be specific to a particular network type. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + * + * @deprecated See {@link NetworkInfo#getExtraInfo()}. + */ + @Deprecated + public static final String EXTRA_EXTRA_INFO = "extraInfo"; + /** + * The lookup key for an int that provides information about + * our connection to the internet at large. 0 indicates no connection, + * 100 indicates a great connection. Retrieve it with + * {@link android.content.Intent#getIntExtra(String, int)}. + * {@hide} + */ + public static final String EXTRA_INET_CONDITION = "inetCondition"; + /** + * The lookup key for a {@link CaptivePortal} object included with the + * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal} + * object can be used to either indicate to the system that the captive + * portal has been dismissed or that the user does not want to pursue + * signing in to captive portal. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; + + /** + * Key for passing a URL to the captive portal login activity. + */ + public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; + + /** + * Key for passing a {@link android.net.captiveportal.CaptivePortalProbeSpec} to the captive + * portal login activity. + * {@hide} + */ + @SystemApi + public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = + "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; + + /** + * Key for passing a user agent string to the captive portal login activity. + * {@hide} + */ + @SystemApi + public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = + "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + + /** + * Broadcast action to indicate the change of data activity status + * (idle or active) on a network in a recent period. + * The network becomes active when data transmission is started, or + * idle if there is no data transmission for a period of time. + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_ACTIVITY_CHANGE = + "android.net.conn.DATA_ACTIVITY_CHANGE"; + /** + * The lookup key for an enum that indicates the network device type on which this data activity + * change happens. + * {@hide} + */ + public static final String EXTRA_DEVICE_TYPE = "deviceType"; + /** + * The lookup key for a boolean that indicates the device is active or not. {@code true} means + * it is actively sending or receiving data and {@code false} means it is idle. + * {@hide} + */ + public static final String EXTRA_IS_ACTIVE = "isActive"; + /** + * The lookup key for a long that contains the timestamp (nanos) of the radio state change. + * {@hide} + */ + public static final String EXTRA_REALTIME_NS = "tsNanos"; + + /** + * Broadcast Action: The setting for background data usage has changed + * values. Use {@link #getBackgroundDataSetting()} to get the current value. + *

+ * If an application uses the network in the background, it should listen + * for this broadcast and stop using the background data if the value is + * {@code false}. + *

+ * + * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability + * of background data depends on several combined factors, and + * this broadcast is no longer sent. Instead, when background + * data is unavailable, {@link #getActiveNetworkInfo()} will now + * appear disconnected. During first boot after a platform + * upgrade, this broadcast will be sent once if + * {@link #getBackgroundDataSetting()} was {@code false} before + * the upgrade. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = + "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + + /** + * Broadcast Action: The network connection may not be good + * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and + * {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify + * the network and it's condition. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage + public static final String INET_CONDITION_ACTION = + "android.net.conn.INET_CONDITION_ACTION"; + + /** + * Broadcast Action: A tetherable connection has come or gone. + * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER}, + * {@code ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY}, + * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER}, and + * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate + * the current state of tethering. Each include a list of + * interface names in that state (may be empty). + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String ACTION_TETHER_STATE_CHANGED = + TetheringManager.ACTION_TETHER_STATE_CHANGED; + + /** + * @hide + * gives a String[] listing all the interfaces configured for + * tethering and currently available for tethering. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_AVAILABLE_TETHER = TetheringManager.EXTRA_AVAILABLE_TETHER; + + /** + * @hide + * gives a String[] listing all the interfaces currently in local-only + * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) + */ + public static final String EXTRA_ACTIVE_LOCAL_ONLY = TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; + + /** + * @hide + * gives a String[] listing all the interfaces currently tethered + * (ie, has DHCPv4 support and packets potentially forwarded/NATed) + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_ACTIVE_TETHER = TetheringManager.EXTRA_ACTIVE_TETHER; + + /** + * @hide + * gives a String[] listing all the interfaces we tried to tether and + * failed. Use {@link #getLastTetherError} to find the error code + * for any interfaces listed here. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_ERRORED_TETHER = TetheringManager.EXTRA_ERRORED_TETHER; + + /** + * Broadcast Action: The captive portal tracker has finished its test. + * Sent only while running Setup Wizard, in lieu of showing a user + * notification. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED = + "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED"; + /** + * The lookup key for a boolean that indicates whether a captive portal was detected. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * @hide + */ + public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal"; + + /** + * Action used to display a dialog that asks the user whether to connect to a network that is + * not validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED"; + + /** + * Action used to display a dialog that asks the user whether to avoid a network that is no + * longer validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_LOST_VALIDATION = + "android.net.conn.PROMPT_LOST_VALIDATION"; + + /** + * Action used to display a dialog that asks the user whether to stay connected to a network + * that has not validated. This intent is used to start the dialog in settings via + * startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = + "android.net.conn.PROMPT_PARTIAL_CONNECTIVITY"; + + /** + * Invalid tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + public static final int TETHERING_INVALID = TetheringManager.TETHERING_INVALID; + + /** + * Wifi tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_WIFI = TetheringManager.TETHERING_WIFI; + + /** + * USB tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_USB = TetheringManager.TETHERING_USB; + + /** + * Bluetooth tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH; + + /** + * Wifi P2p tethering type. + * Wifi P2p tethering is set through events automatically, and don't + * need to start from #startTethering(int, boolean, OnStartTetheringCallback). + * @hide + */ + public static final int TETHERING_WIFI_P2P = TetheringManager.TETHERING_WIFI_P2P; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. + * @hide + */ + public static final String EXTRA_ADD_TETHER_TYPE = TetheringConstants.EXTRA_ADD_TETHER_TYPE; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering for + * which to cancel provisioning. + * @hide + */ + public static final String EXTRA_REM_TETHER_TYPE = TetheringConstants.EXTRA_REM_TETHER_TYPE; + + /** + * Extra used for communicating with the TetherService. True to schedule a recheck of tether + * provisioning. + * @hide + */ + public static final String EXTRA_SET_ALARM = TetheringConstants.EXTRA_SET_ALARM; + + /** + * Tells the TetherService to run a provision check now. + * @hide + */ + public static final String EXTRA_RUN_PROVISION = TetheringConstants.EXTRA_RUN_PROVISION; + + /** + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left empty. + * @hide + */ + public static final String EXTRA_PROVISION_CALLBACK = + TetheringConstants.EXTRA_PROVISION_CALLBACK; + + /** + * The absence of a connection type. + * @hide + */ + @SystemApi + public static final int TYPE_NONE = -1; + + /** + * A Mobile data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE = 0; + + /** + * A WIFI data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIFI = 1; + + /** + * An MMS-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Multimedia Messaging Service servers. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_MMS = 2; + + /** + * A SUPL-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Secure User Plane Location servers for help locating the device. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_SUPL = 3; + + /** + * A DUN-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is sometimes by the system when setting up an upstream connection + * for tethering so that the carrier is aware of DUN traffic. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_DUN} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_DUN = 4; + + /** + * A High Priority Mobile data connection. This network type uses the + * same network interface as {@link #TYPE_MOBILE} but the routing setup + * is different. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE_HIPRI = 5; + + /** + * A WiMAX data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIMAX = 6; + + /** + * A Bluetooth data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_BLUETOOTH = 7; + + /** + * Fake data connection. This should not be used on shipping devices. + * @deprecated This is not used any more. + */ + @Deprecated + public static final int TYPE_DUMMY = 8; + + /** + * An Ethernet data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_ETHERNET = 9; + + /** + * Over the air Administration. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_FOTA = 10; + + /** + * IP Multimedia Subsystem. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IMS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage + public static final int TYPE_MOBILE_IMS = 11; + + /** + * Carrier Branded Services. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_CBS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_CBS = 12; + + /** + * A Wi-Fi p2p connection. Only requesting processes will have access to + * the peers connected. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_WIFI_P2P} instead. + * {@hide} + */ + @Deprecated + @SystemApi + public static final int TYPE_WIFI_P2P = 13; + + /** + * The network to use for initially attaching to the network + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IA} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage + public static final int TYPE_MOBILE_IA = 14; + + /** + * Emergency PDN connection for emergency services. This + * may include IMS and MMS in emergency situations. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_EIMS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_EMERGENCY = 15; + + /** + * The network that uses proxy to achieve connectivity. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @SystemApi + public static final int TYPE_PROXY = 16; + + /** + * A virtual network using one or more native bearers. + * It may or may not be providing security services. + * @deprecated Applications should use {@link NetworkCapabilities#TRANSPORT_VPN} instead. + */ + @Deprecated + public static final int TYPE_VPN = 17; + + /** + * A network that is exclusively meant to be used for testing + * + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static final int TYPE_TEST = 18; // TODO: Remove this once NetworkTypes are unused. + + /** + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_NONE, + TYPE_MOBILE, + TYPE_WIFI, + TYPE_MOBILE_MMS, + TYPE_MOBILE_SUPL, + TYPE_MOBILE_DUN, + TYPE_MOBILE_HIPRI, + TYPE_WIMAX, + TYPE_BLUETOOTH, + TYPE_DUMMY, + TYPE_ETHERNET, + TYPE_MOBILE_FOTA, + TYPE_MOBILE_IMS, + TYPE_MOBILE_CBS, + TYPE_WIFI_P2P, + TYPE_MOBILE_IA, + TYPE_MOBILE_EMERGENCY, + TYPE_PROXY, + TYPE_VPN, + TYPE_TEST + }) + public @interface LegacyNetworkType {} + + // Deprecated constants for return values of startUsingNetworkFeature. They used to live + // in com.android.internal.telephony.PhoneConstants until they were made inaccessible. + private static final int DEPRECATED_PHONE_CONSTANT_APN_ALREADY_ACTIVE = 0; + private static final int DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED = 1; + private static final int DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED = 3; + + /** {@hide} */ + public static final int MAX_RADIO_TYPE = TYPE_TEST; + + /** {@hide} */ + public static final int MAX_NETWORK_TYPE = TYPE_TEST; + + private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; + + /** + * If you want to set the default network preference,you can directly + * change the networkAttributes array in framework's config.xml. + * + * @deprecated Since we support so many more networks now, the single + * network default network preference can't really express + * the hierarchy. Instead, the default is defined by the + * networkAttributes in config.xml. You can determine + * the current value by calling {@link #getNetworkPreference()} + * from an App. + */ + @Deprecated + public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI; + + /** + * @hide + */ + public static final int REQUEST_ID_UNSET = 0; + + /** + * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered. + * This allows to distinguish when unregistering NetworkCallbacks those that were never + * registered from those that were already unregistered. + * @hide + */ + private static final NetworkRequest ALREADY_UNREGISTERED = + new NetworkRequest.Builder().clearCapabilities().build(); + + /** + * A NetID indicating no Network is selected. + * Keep in sync with bionic/libc/dns/include/resolv_netid.h + * @hide + */ + public static final int NETID_UNSET = 0; + + /** + * Private DNS Mode values. + * + * The "private_dns_mode" global setting stores a String value which is + * expected to be one of the following. + */ + + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_OFF = "off"; + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; + /** + * The default Private DNS mode. + * + * This may change from release to release or may become dependent upon + * the capabilities of the underlying platform. + * + * @hide + */ + public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + private final IConnectivityManager mService; + /** + * A kludge to facilitate static access where a Context pointer isn't available, like in the + * case of the static set/getProcessDefaultNetwork methods and from the Network class. + * TODO: Remove this after deprecating the static methods in favor of non-static methods or + * methods that take a Context argument. + */ + private static ConnectivityManager sInstance; + + private final Context mContext; + + private INetworkManagementService mNMService; + private INetworkPolicyManager mNPManager; + private final TetheringManager mTetheringManager; + + /** + * Tests if a given integer represents a valid network type. + * @param networkType the type to be tested + * @return a boolean. {@code true} if the type is valid, else {@code false} + * @deprecated All APIs accepting a network type are deprecated. There should be no need to + * validate a network type. + */ + @Deprecated + public static boolean isNetworkTypeValid(int networkType) { + return MIN_NETWORK_TYPE <= networkType && networkType <= MAX_NETWORK_TYPE; + } + + /** + * Returns a non-localized string representing a given network type. + * ONLY used for debugging output. + * @param type the type needing naming + * @return a String for the given type, or a string version of the type ("87") + * if no name is known. + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static String getNetworkTypeName(int type) { + switch (type) { + case TYPE_NONE: + return "NONE"; + case TYPE_MOBILE: + return "MOBILE"; + case TYPE_WIFI: + return "WIFI"; + case TYPE_MOBILE_MMS: + return "MOBILE_MMS"; + case TYPE_MOBILE_SUPL: + return "MOBILE_SUPL"; + case TYPE_MOBILE_DUN: + return "MOBILE_DUN"; + case TYPE_MOBILE_HIPRI: + return "MOBILE_HIPRI"; + case TYPE_WIMAX: + return "WIMAX"; + case TYPE_BLUETOOTH: + return "BLUETOOTH"; + case TYPE_DUMMY: + return "DUMMY"; + case TYPE_ETHERNET: + return "ETHERNET"; + case TYPE_MOBILE_FOTA: + return "MOBILE_FOTA"; + case TYPE_MOBILE_IMS: + return "MOBILE_IMS"; + case TYPE_MOBILE_CBS: + return "MOBILE_CBS"; + case TYPE_WIFI_P2P: + return "WIFI_P2P"; + case TYPE_MOBILE_IA: + return "MOBILE_IA"; + case TYPE_MOBILE_EMERGENCY: + return "MOBILE_EMERGENCY"; + case TYPE_PROXY: + return "PROXY"; + case TYPE_VPN: + return "VPN"; + default: + return Integer.toString(type); + } + } + + /** + * @hide + * TODO: Expose for SystemServer when becomes a module. + */ + public void systemReady() { + try { + mService.systemReady(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Checks if a given type uses the cellular data connection. + * This should be replaced in the future by a network property. + * @param networkType the type to check + * @return a boolean - {@code true} if uses cellular network, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static boolean isNetworkTypeMobile(int networkType) { + switch (networkType) { + case TYPE_MOBILE: + case TYPE_MOBILE_MMS: + case TYPE_MOBILE_SUPL: + case TYPE_MOBILE_DUN: + case TYPE_MOBILE_HIPRI: + case TYPE_MOBILE_FOTA: + case TYPE_MOBILE_IMS: + case TYPE_MOBILE_CBS: + case TYPE_MOBILE_IA: + case TYPE_MOBILE_EMERGENCY: + return true; + default: + return false; + } + } + + /** + * Checks if the given network type is backed by a Wi-Fi radio. + * + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static boolean isNetworkTypeWifi(int networkType) { + switch (networkType) { + case TYPE_WIFI: + case TYPE_WIFI_P2P: + return true; + default: + return false; + } + } + + /** + * Specifies the preferred network type. When the device has more + * than one type available the preferred network type will be used. + * + * @param preference the network type to prefer over all others. It is + * unspecified what happens to the old preferred network in the + * overall ordering. + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + public void setNetworkPreference(int preference) { + } + + /** + * Retrieves the current preferred network type. + * + * @return an integer representing the preferred network type + * + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public int getNetworkPreference() { + return TYPE_NONE; + } + + /** + * Returns details about the currently active default data network. When + * connected, this network is the default route for outgoing connections. + * You should always check {@link NetworkInfo#isConnected()} before initiating + * network traffic. This may return {@code null} when there is no default + * network. + * Note that if the default network is a VPN, this method will return the + * NetworkInfo for one of its underlying networks instead, or null if the + * VPN agent did not specify any. Apps interested in learning about VPNs + * should use {@link #getNetworkInfo(android.net.Network)} instead. + * + * @return a {@link NetworkInfo} object for the current default network + * or {@code null} if no default network is currently active + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getActiveNetworkInfo() { + try { + return mService.getActiveNetworkInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network. In the event that the current active default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network. + * + * @return a {@link Network} object for the current default network or + * {@code null} if no default network is currently active + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public Network getActiveNetwork() { + try { + return mService.getActiveNetwork(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network for a specific UID. In the event that the default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network for the UID. + * + * @return a {@link Network} object for the current default network for the + * given UID or {@code null} if no default network is currently active + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + @Nullable + public Network getActiveNetworkForUid(int uid) { + return getActiveNetworkForUid(uid, false); + } + + /** {@hide} */ + public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) { + try { + return mService.getActiveNetworkForUid(uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + *

    + *
  • target {@link VERSION_CODES#N API 24} or above, and + *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} + * meta-data field. + *
+ * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + try { + return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + *

The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @param lockdownAllowlist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be allowed is installed or + * uninstalled. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List lockdownAllowlist) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownAllowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public String getAlwaysOnVpnPackageForUser(int userId) { + try { + return mService.getAlwaysOnVpnPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List getVpnLockdownWhitelist(int userId) { + try { + return mService.getVpnLockdownWhitelist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds or removes a requirement for given UID ranges to use the VPN. + * + * If set to {@code true}, informs the system that the UIDs in the specified ranges must not + * have any connectivity except if a VPN is connected and applies to the UIDs, or if the UIDs + * otherwise have permission to bypass the VPN (e.g., because they have the + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission, or when + * using a socket protected by a method such as {@link VpnService#protect(DatagramSocket)}. If + * set to {@code false}, a previously-added restriction is removed. + *

+ * Each of the UID ranges specified by this method is added and removed as is, and no processing + * is performed on the ranges to de-duplicate, merge, split, or intersect them. In order to + * remove a previously-added range, the exact range must be removed as is. + *

+ * The changes are applied asynchronously and may not have been applied by the time the method + * returns. Apps will be notified about any changes that apply to them via + * {@link NetworkCallback#onBlockedStatusChanged} callbacks called after the changes take + * effect. + *

+ * This method should be called only by the VPN code. + * + * @param ranges the UID ranges to restrict + * @param requireVpn whether the specified UID ranges must use a VPN + * + * TODO: expose as @SystemApi. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void setRequireVpnForUids(boolean requireVpn, + @NonNull Collection> ranges) { + Objects.requireNonNull(ranges); + // The Range class is not parcelable. Convert to UidRange, which is what is used internally. + // This method is not necessarily expected to be used outside the system server, so + // parceling may not be necessary, but it could be used out-of-process, e.g., by the network + // stack process, or by tests. + UidRange[] rangesArray = new UidRange[ranges.size()]; + int index = 0; + for (Range range : ranges) { + rangesArray[index++] = new UidRange(range.getLower(), range.getUpper()); + } + try { + mService.setRequireVpnForUids(requireVpn, rangesArray); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns details about the currently active default data network + * for a given uid. This is for internal use only to avoid spying + * other apps. + * + * @return a {@link NetworkInfo} object for the current default network + * for the given uid or {@code null} if no default network is + * available for the specified uid. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public NetworkInfo getActiveNetworkInfoForUid(int uid) { + return getActiveNetworkInfoForUid(uid, false); + } + + /** {@hide} */ + public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { + try { + return mService.getActiveNetworkInfoForUid(uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about a particular + * network type. + * + * @param networkType integer specifying which networkType in + * which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network type or {@code null} if the type is not + * supported by the device. If {@code networkType} is + * TYPE_VPN and a VPN is active for the calling app, + * then this method will try to return one of the + * underlying networks for the VPN or null if the + * VPN agent didn't specify any. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getNetworkInfo(int networkType) { + try { + return mService.getNetworkInfo(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about a particular + * Network. + * + * @param network {@link Network} specifying which network + * in which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network or {@code null} if the {@code Network} + * is not valid. + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getNetworkInfo(@Nullable Network network) { + return getNetworkInfoForUid(network, Process.myUid(), false); + } + + /** {@hide} */ + public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) { + try { + return mService.getNetworkInfoForUid(network, uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about all network + * types supported by the device. + * + * @return an array of {@link NetworkInfo} objects. Check each + * {@link NetworkInfo#getType} for which type each applies. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @NonNull + public NetworkInfo[] getAllNetworkInfo() { + try { + return mService.getAllNetworkInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the {@link Network} object currently serving a given type, or + * null if the given type is not connected. + * + * @hide + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + public Network getNetworkForType(int networkType) { + try { + return mService.getNetworkForType(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns an array of all {@link Network} currently tracked by the + * framework. + * + * @return an array of {@link Network} objects. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @NonNull + public Network[] getAllNetworks() { + try { + return mService.getAllNetworks(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns an array of {@link android.net.NetworkCapabilities} objects, representing + * the Networks that applications run by the given user will use by default. + * @hide + */ + @UnsupportedAppUsage + public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { + try { + return mService.getDefaultNetworkCapabilitiesForUser( + userId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the IP information for the current default network. + * + * @return a {@link LinkProperties} object describing the IP info + * for the current default network, or {@code null} if there + * is no current default network. + * + * {@hide} + * @deprecated please use {@link #getLinkProperties(Network)} on the return + * value of {@link #getActiveNetwork()} instead. In particular, + * this method will return non-null LinkProperties even if the + * app is blocked by policy from using this network. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 109783091) + public LinkProperties getActiveLinkProperties() { + try { + return mService.getActiveLinkProperties(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the IP information for a given network type. + * + * @param networkType the network type of interest. + * @return a {@link LinkProperties} object describing the IP info + * for the given networkType, or {@code null} if there is + * no current default network. + * + * {@hide} + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks}, + * {@link #getNetworkInfo(android.net.Network)}, and + * {@link #getLinkProperties(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public LinkProperties getLinkProperties(int networkType) { + try { + return mService.getLinkPropertiesForType(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the {@link LinkProperties} for the given {@link Network}. This + * will return {@code null} if the network is unknown. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link LinkProperties} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public LinkProperties getLinkProperties(@Nullable Network network) { + try { + return mService.getLinkProperties(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This + * will return {@code null} if the network is unknown. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) { + try { + return mService.getNetworkCapabilities(network, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets a URL that can be used for resolving whether a captive portal is present. + * 1. This URL should respond with a 204 response to a GET request to indicate no captive + * portal is present. + * 2. This URL must be HTTP as redirect responses are used to find captive portal + * sign-in pages. Captive portals cannot respond to HTTPS requests with redirects. + * + * The system network validation may be using different strategies to detect captive portals, + * so this method does not necessarily return a URL used by the system. It only returns a URL + * that may be relevant for other components trying to detect captive portals. + * + * @hide + * @deprecated This API returns URL which is not guaranteed to be one of the URLs used by the + * system. + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public String getCaptivePortalServerUrl() { + try { + return mService.getCaptivePortalServerUrl(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Tells the underlying networking system that the caller wants to + * begin using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature to be used + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int startUsingNetworkFeature(int networkType, String feature) { + checkLegacyRoutingApiAccess(); + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " + + feature); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED; + } + + NetworkRequest request = null; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) { + Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest); + renewRequestLocked(l); + if (l.currentNetwork != null) { + return DEPRECATED_PHONE_CONSTANT_APN_ALREADY_ACTIVE; + } else { + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED; + } + } + + request = requestNetworkForFeatureLocked(netCap); + } + if (request != null) { + Log.d(TAG, "starting startUsingNetworkFeature for request " + request); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED; + } else { + Log.d(TAG, " request Failed"); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED; + } + } + + /** + * Tells the underlying networking system that the caller is finished + * using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature that is no longer needed + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #unregisterNetworkCallback(NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int stopUsingNetworkFeature(int networkType, String feature) { + checkLegacyRoutingApiAccess(); + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " + + feature); + return -1; + } + + if (removeRequestForFeature(netCap)) { + Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature); + } + return 1; + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) { + if (networkType == TYPE_MOBILE) { + switch (feature) { + case "enableCBS": + return networkCapabilitiesForType(TYPE_MOBILE_CBS); + case "enableDUN": + case "enableDUNAlways": + return networkCapabilitiesForType(TYPE_MOBILE_DUN); + case "enableFOTA": + return networkCapabilitiesForType(TYPE_MOBILE_FOTA); + case "enableHIPRI": + return networkCapabilitiesForType(TYPE_MOBILE_HIPRI); + case "enableIMS": + return networkCapabilitiesForType(TYPE_MOBILE_IMS); + case "enableMMS": + return networkCapabilitiesForType(TYPE_MOBILE_MMS); + case "enableSUPL": + return networkCapabilitiesForType(TYPE_MOBILE_SUPL); + default: + return null; + } + } else if (networkType == TYPE_WIFI && "p2p".equals(feature)) { + return networkCapabilitiesForType(TYPE_WIFI_P2P); + } + return null; + } + + private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) { + if (netCap == null) return TYPE_NONE; + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { + return TYPE_MOBILE_CBS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) { + return TYPE_MOBILE_IMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) { + return TYPE_MOBILE_FOTA; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) { + return TYPE_MOBILE_DUN; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) { + return TYPE_MOBILE_SUPL; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) { + return TYPE_MOBILE_MMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + return TYPE_MOBILE_HIPRI; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) { + return TYPE_WIFI_P2P; + } + return TYPE_NONE; + } + + private static class LegacyRequest { + NetworkCapabilities networkCapabilities; + NetworkRequest networkRequest; + int expireSequenceNumber; + Network currentNetwork; + int delay = -1; + + private void clearDnsBinding() { + if (currentNetwork != null) { + currentNetwork = null; + setProcessDefaultNetworkForHostResolution(null); + } + } + + NetworkCallback networkCallback = new NetworkCallback() { + @Override + public void onAvailable(Network network) { + currentNetwork = network; + Log.d(TAG, "startUsingNetworkFeature got Network:" + network); + setProcessDefaultNetworkForHostResolution(network); + } + @Override + public void onLost(Network network) { + if (network.equals(currentNetwork)) clearDnsBinding(); + Log.d(TAG, "startUsingNetworkFeature lost Network:" + network); + } + }; + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private static final HashMap sLegacyRequests = + new HashMap<>(); + + private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) { + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) return l.networkRequest; + } + return null; + } + + private void renewRequestLocked(LegacyRequest l) { + l.expireSequenceNumber++; + Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber); + sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay); + } + + private void expireRequest(NetworkCapabilities netCap, int sequenceNum) { + int ourSeqNum = -1; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l == null) return; + ourSeqNum = l.expireSequenceNumber; + if (l.expireSequenceNumber == sequenceNum) removeRequestForFeature(netCap); + } + Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum); + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) { + int delay = -1; + int type = legacyTypeForNetworkCapabilities(netCap); + try { + delay = mService.getRestoreDefaultNetworkDelay(type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + LegacyRequest l = new LegacyRequest(); + l.networkCapabilities = netCap; + l.delay = delay; + l.expireSequenceNumber = 0; + l.networkRequest = sendRequestForNetwork( + netCap, l.networkCallback, 0, REQUEST, type, getDefaultHandler()); + if (l.networkRequest == null) return null; + sLegacyRequests.put(netCap, l); + sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay); + return l.networkRequest; + } + + private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) { + if (delay >= 0) { + Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay); + CallbackHandler handler = getDefaultHandler(); + Message msg = handler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap); + handler.sendMessageDelayed(msg, delay); + } + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private boolean removeRequestForFeature(NetworkCapabilities netCap) { + final LegacyRequest l; + synchronized (sLegacyRequests) { + l = sLegacyRequests.remove(netCap); + } + if (l == null) return false; + unregisterNetworkCallback(l.networkCallback); + l.clearDnsBinding(); + return true; + } + + private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray(); + static { + sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_CBS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_FOTA, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_IMS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_MMS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_SUPL, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_WIFI_P2P, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH); + sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET); + } + + private static final SparseIntArray sLegacyTypeToCapability = new SparseIntArray(); + static { + sLegacyTypeToCapability.put(TYPE_MOBILE_CBS, NetworkCapabilities.NET_CAPABILITY_CBS); + sLegacyTypeToCapability.put(TYPE_MOBILE_DUN, NetworkCapabilities.NET_CAPABILITY_DUN); + sLegacyTypeToCapability.put(TYPE_MOBILE_FOTA, NetworkCapabilities.NET_CAPABILITY_FOTA); + sLegacyTypeToCapability.put(TYPE_MOBILE_IMS, NetworkCapabilities.NET_CAPABILITY_IMS); + sLegacyTypeToCapability.put(TYPE_MOBILE_MMS, NetworkCapabilities.NET_CAPABILITY_MMS); + sLegacyTypeToCapability.put(TYPE_MOBILE_SUPL, NetworkCapabilities.NET_CAPABILITY_SUPL); + sLegacyTypeToCapability.put(TYPE_WIFI_P2P, NetworkCapabilities.NET_CAPABILITY_WIFI_P2P); + } + + /** + * Given a legacy type (TYPE_WIFI, ...) returns a NetworkCapabilities + * instance suitable for registering a request or callback. Throws an + * IllegalArgumentException if no mapping from the legacy type to + * NetworkCapabilities is known. + * + * @deprecated Types are deprecated. Use {@link NetworkCallback} or {@link NetworkRequest} + * to find the network instead. + * @hide + */ + public static NetworkCapabilities networkCapabilitiesForType(int type) { + final NetworkCapabilities nc = new NetworkCapabilities(); + + // Map from type to transports. + final int NOT_FOUND = -1; + final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND); + Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type); + nc.addTransportType(transport); + + // Map from type to capabilities. + nc.addCapability(sLegacyTypeToCapability.get( + type, NetworkCapabilities.NET_CAPABILITY_INTERNET)); + nc.maybeMarkCapabilitiesRestricted(); + return nc; + } + + /** @hide */ + public static class PacketKeepaliveCallback { + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public PacketKeepaliveCallback() { + } + /** The requested keepalive was successfully started. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onStarted() {} + /** The keepalive was successfully stopped. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onStopped() {} + /** An error occurred. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onError(int error) {} + } + + /** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link ConnectivityManager.PacketKeepalive} object, such as {@link #startNattKeepalive}, + * passing in a non-null callback. If the callback is successfully started, the callback's + * {@code onStarted} method will be called. If an error occurs, {@code onError} will be called, + * specifying one of the {@code ERROR_*} constants in this class. + * + * To stop an existing keepalive, call {@link PacketKeepalive#stop}. The system will call + * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or + * {@link PacketKeepaliveCallback#onError} if an error occurred. + * + * @deprecated Use {@link SocketKeepalive} instead. + * + * @hide + */ + public class PacketKeepalive { + + private static final String TAG = "PacketKeepalive"; + + /** @hide */ + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + /** The minimum interval in seconds between keepalive packet transmissions */ + public static final int MIN_INTERVAL = 10; + + private final Network mNetwork; + private final ISocketKeepaliveCallback mCallback; + private final ExecutorService mExecutor; + + private volatile Integer mSlot; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void stop() { + try { + mExecutor.execute(() -> { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } catch (RejectedExecutionException e) { + // The internal executor has already stopped due to previous event. + } + } + + private PacketKeepalive(Network network, PacketKeepaliveCallback callback) { + Preconditions.checkNotNull(network, "network cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + mNetwork = network; + mExecutor = Executors.newSingleThreadExecutor(); + mCallback = new ISocketKeepaliveCallback.Stub() { + @Override + public void onStarted(int slot) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = slot; + callback.onStarted(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onStopped() { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = null; + callback.onStopped(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + mExecutor.shutdown(); + } + + @Override + public void onError(int error) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = null; + callback.onError(error); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + mExecutor.shutdown(); + } + + @Override + public void onDataReceived() { + // PacketKeepalive is only used for Nat-T keepalive and as such does not invoke + // this callback when data is received. + } + }; + } + } + + /** + * Starts an IPsec NAT-T keepalive packet with the specified parameters. + * + * @deprecated Use {@link #createSocketKeepalive} instead. + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public PacketKeepalive startNattKeepalive( + Network network, int intervalSeconds, PacketKeepaliveCallback callback, + InetAddress srcAddr, int srcPort, InetAddress dstAddr) { + final PacketKeepalive k = new PacketKeepalive(network, callback); + try { + mService.startNattKeepalive(network, intervalSeconds, k.mCallback, + srcAddr.getHostAddress(), srcPort, dstAddr.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + return k; + } + + // Construct an invalid fd. + private ParcelFileDescriptor createInvalidFd() { + final int invalidFd = -1; + return ParcelFileDescriptor.adoptFd(invalidFd); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + **/ + public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull UdpEncapsulationSocket socket, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + // Dup is needed here as the pfd inside the socket is owned by the IpSecService, + // which cannot be obtained by the app process. + dup = ParcelFileDescriptor.dup(socket.getFileDescriptor()); + } catch (IOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new NattSocketKeepalive(mService, network, dup, socket.getResourceId(), source, + destination, executor, callback); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called + * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}. + * + * @param network The {@link Network} the socket is on. + * @param pfd The {@link ParcelFileDescriptor} that needs to be kept alive. The provided + * {@link ParcelFileDescriptor} must be bound to a port and the keepalives will be sent + * from that port. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. The + * keepalive packets will always be sent to port 4500 of the given {@code destination}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public @NonNull SocketKeepalive createNattKeepalive(@NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + // TODO: Consider remove unnecessary dup. + dup = pfd.dup(); + } catch (IOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new NattSocketKeepalive(mService, network, dup, + INVALID_RESOURCE_ID /* Unused */, source, destination, executor, callback); + } + + /** + * Request that keepalives be started on a TCP socket. + * The socket must be established. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param executor The executor on which callback will be invoked. This implementation assumes + * the provided {@link Executor} runs the callbacks in sequence with no + * concurrency. Failing this, no guarantee of correctness can be made. It is + * the responsibility of the caller to ensure the executor provides this + * guarantee. A simple way of creating such an executor is with the standard + * tool {@code Executors.newSingleThreadExecutor}. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull Socket socket, + @NonNull Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + dup = ParcelFileDescriptor.fromSocket(socket); + } catch (UncheckedIOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new TcpSocketKeepalive(mService, network, dup, executor, callback); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * + * @deprecated Deprecated in favor of the + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public boolean requestRouteToHost(int networkType, int hostAddress) { + return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress)); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * @hide + * @deprecated Deprecated in favor of the {@link #requestNetwork} and + * {@link #bindProcessToNetwork} API. + */ + @Deprecated + @UnsupportedAppUsage + public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { + checkLegacyRoutingApiAccess(); + try { + return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress(), + mContext.getOpPackageName(), getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the context's attribution tag + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private @Nullable String getAttributionTag() { + return mContext.getAttributionTag(); + } + + /** + * Returns the value of the setting for background data usage. If false, + * applications should not use the network if the application is not in the + * foreground. Developers should respect this setting, and check the value + * of this before performing any background data operations. + *

+ * All applications that have background services that use the network + * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}. + *

+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of + * background data depends on several combined factors, and this method will + * always return {@code true}. Instead, when background data is unavailable, + * {@link #getActiveNetworkInfo()} will now appear disconnected. + * + * @return Whether background data usage is allowed. + */ + @Deprecated + public boolean getBackgroundDataSetting() { + // assume that background data is allowed; final authority is + // NetworkInfo which may be blocked. + return true; + } + + /** + * Sets the value of the setting for background data usage. + * + * @param allowBackgroundData Whether an application should use data while + * it is in the background. + * + * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING + * @see #getBackgroundDataSetting() + * @hide + */ + @Deprecated + @UnsupportedAppUsage + public void setBackgroundDataSetting(boolean allowBackgroundData) { + // ignored + } + + /** + * @hide + * @deprecated Talk to TelephonyManager directly + */ + @Deprecated + @UnsupportedAppUsage + public boolean getMobileDataEnabled() { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + if (tm != null) { + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId); + boolean retVal = tm.createForSubscriptionId(subId).isDataEnabled(); + Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId + + " retVal=" + retVal); + return retVal; + } + Log.d("ConnectivityManager", "getMobileDataEnabled()- remote exception retVal=false"); + return false; + } + + /** + * Callback for use with {@link ConnectivityManager#addDefaultNetworkActiveListener} + * to find out when the system default network has gone in to a high power state. + */ + public interface OnNetworkActiveListener { + /** + * Called on the main thread of the process to report that the current data network + * has become active, and it is now a good time to perform any pending network + * operations. Note that this listener only tells you when the network becomes + * active; if at any other time you want to know whether it is active (and thus okay + * to initiate network traffic), you can retrieve its instantaneous state with + * {@link ConnectivityManager#isDefaultNetworkActive}. + */ + void onNetworkActive(); + } + + private INetworkManagementService getNetworkManagementService() { + synchronized (this) { + if (mNMService != null) { + return mNMService; + } + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + mNMService = INetworkManagementService.Stub.asInterface(b); + return mNMService; + } + } + + private final ArrayMap + mNetworkActivityListeners = new ArrayMap<>(); + + /** + * Start listening to reports when the system's default data network is active, meaning it is + * a good time to perform network traffic. Use {@link #isDefaultNetworkActive()} + * to determine the current state of the system's default network after registering the + * listener. + *

+ * If the process default network has been set with + * {@link ConnectivityManager#bindProcessToNetwork} this function will not + * reflect the process's default, but the system default. + * + * @param l The listener to be told when the network is active. + */ + public void addDefaultNetworkActiveListener(final OnNetworkActiveListener l) { + INetworkActivityListener rl = new INetworkActivityListener.Stub() { + @Override + public void onNetworkActive() throws RemoteException { + l.onNetworkActive(); + } + }; + + try { + getNetworkManagementService().registerNetworkActivityListener(rl); + mNetworkActivityListeners.put(l, rl); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove network active listener previously registered with + * {@link #addDefaultNetworkActiveListener}. + * + * @param l Previously registered listener. + */ + public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) { + INetworkActivityListener rl = mNetworkActivityListeners.get(l); + Preconditions.checkArgument(rl != null, "Listener was not registered."); + try { + getNetworkManagementService().unregisterNetworkActivityListener(rl); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return whether the data network is currently active. An active network means that + * it is currently in a high power state for performing data transmission. On some + * types of networks, it may be expensive to move and stay in such a state, so it is + * more power efficient to batch network traffic together when the radio is already in + * this state. This method tells you whether right now is currently a good time to + * initiate network traffic, as the network is already active. + */ + public boolean isDefaultNetworkActive() { + try { + return getNetworkManagementService().isNetworkActive(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * {@hide} + */ + public ConnectivityManager(Context context, IConnectivityManager service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE); + sInstance = this; + } + + /** {@hide} */ + @UnsupportedAppUsage + public static ConnectivityManager from(Context context) { + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + /** @hide */ + public NetworkRequest getDefaultRequest() { + try { + // This is not racy as the default request is final in ConnectivityService. + return mService.getDefaultRequest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /* TODO: These permissions checks don't belong in client-side code. Move them to + * services.jar, possibly in com.android.server.net. */ + + /** {@hide} */ + public static final void enforceChangePermission(Context context, + String callingPkg, String callingAttributionTag) { + int uid = Binder.getCallingUid(); + checkAndNoteChangeNetworkStateOperation(context, uid, callingPkg, + callingAttributionTag, true /* throwException */); + } + + /** + * Check if the package is a allowed to change the network state. This also accounts that such + * an access happened. + * + * @return {@code true} iff the package is allowed to change the network state. + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private static boolean checkAndNoteChangeNetworkStateOperation(@NonNull Context context, + int uid, @NonNull String callingPackage, @Nullable String callingAttributionTag, + boolean throwException) { + return Settings.checkAndNoteChangeNetworkStateOperation(context, uid, callingPackage, + callingAttributionTag, throwException); + } + + /** + * Check if the package is a allowed to write settings. This also accounts that such an access + * happened. + * + * @return {@code true} iff the package is allowed to write settings. + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, @Nullable String callingAttributionTag, + boolean throwException) { + return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage, + callingAttributionTag, throwException); + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + static ConnectivityManager getInstanceOrNull() { + return sInstance; + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + @UnsupportedAppUsage + private static ConnectivityManager getInstance() { + if (getInstanceOrNull() == null) { + throw new IllegalStateException("No ConnectivityManager yet constructed"); + } + return getInstanceOrNull(); + } + + /** + * Get the set of tetherable, available interfaces. This list is limited by + * device configuration and current interface existence. + * + * @return an array of 0 or more Strings of tetherable interface names. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableIfaces() { + return mTetheringManager.getTetherableIfaces(); + } + + /** + * Get the set of tethered interfaces. + * + * @return an array of 0 or more String of currently tethered interface names. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetheredIfaces() { + return mTetheringManager.getTetheredIfaces(); + } + + /** + * Get the set of interface names which attempted to tether but + * failed. Re-attempting to tether may cause them to reset to the Tethered + * state. Alternatively, causing the interface to be destroyed and recreated + * may cause them to reset to the available state. + * {@link ConnectivityManager#getLastTetherError} can be used to get more + * information on the cause of the errors. + * + * @return an array of 0 or more String indicating the interface names + * which failed to tether. + * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetheringErroredIfaces() { + return mTetheringManager.getTetheringErroredIfaces(); + } + + /** + * Get the set of tethered dhcp ranges. + * + * @deprecated This method is not supported. + * TODO: remove this function when all of clients are removed. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + @Deprecated + public String[] getTetheredDhcpRanges() { + throw new UnsupportedOperationException("getTetheredDhcpRanges is not supported"); + } + + /** + * Attempt to tether the named interface. This will setup a dhcp server + * on the interface, forward and NAT IP packets and forward DNS requests + * to the best active upstream network interface. Note that if no upstream + * IP network interface is available, dhcp will still run and traffic will be + * allowed between the tethered devices and this device, though upstream net + * access will of course fail until an upstream network interface becomes + * active. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public int tether(String iface) { + return mTetheringManager.tether(iface); + } + + /** + * Stop tethering the named interface. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @param iface the interface name to untether. + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * + * {@hide} + */ + @UnsupportedAppUsage + @Deprecated + public int untether(String iface) { + return mTetheringManager.untether(iface); + } + + /** + * Check if the device allows for tethering. It may be disabled via + * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or + * due to device configuration. + * + *

If this app does not have permission to use this API, it will always + * return false rather than throw an exception.

+ * + *

If the device has a hotspot provisioning app, the caller is required to hold the + * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.

+ * + *

Otherwise, this method requires the caller to hold the ability to modify system + * settings as determined by {@link android.provider.Settings.System#canWrite}.

+ * + * @return a boolean - {@code true} indicating Tethering is supported. + * + * @deprecated Use {@link TetheringEventCallback#onTetheringSupported(boolean)} instead. + * {@hide} + */ + @SystemApi + @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED, + android.Manifest.permission.WRITE_SETTINGS}) + public boolean isTetheringSupported() { + return mTetheringManager.isTetheringSupported(); + } + + /** + * 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. + */ + public void onTetheringStarted() {} + + /** + * Called when starting tethering failed. + */ + public void onTetheringFailed() {} + } + + /** + * 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) { + startTethering(type, showProvisioningUi, callback, null); + } + + /** + * Runs tether provisioning for the given type if needed and then starts tethering if + * the check succeeds. If no carrier provisioning is required for tethering, tethering is + * enabled immediately. If provisioning fails, tethering will not be enabled. It also + * schedules tether provisioning re-checks if appropriate. + * + * @param type The type of tethering to start. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there + * is one. This should be true the first time this function is called and also any time + * the user can see this UI. It gives users information from their carrier about the + * check failing and how they can sign up for tethering if possible. + * @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."); + + final Executor executor = new Executor() { + @Override + public void execute(Runnable command) { + if (handler == null) { + command.run(); + } else { + handler.post(command); + } + } + }; + + final StartTetheringCallback tetheringCallback = new StartTetheringCallback() { + @Override + public void onTetheringStarted() { + callback.onTetheringStarted(); + } + + @Override + public void onTetheringFailed(final int error) { + callback.onTetheringFailed(); + } + }; + + final TetheringRequest request = new TetheringRequest.Builder(type) + .setShouldShowEntitlementUi(showProvisioningUi).build(); + + mTetheringManager.startTethering(request, executor, tetheringCallback); + } + + /** + * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if + * applicable. + * + * @param type The type of tethering to stop. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * + * @deprecated Use {@link TetheringManager#stopTethering} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void stopTethering(int type) { + mTetheringManager.stopTethering(type); + } + + /** + * Callback for use with {@link registerTetheringEventCallback} to find out tethering + * upstream status. + * + * @deprecated Use {@link TetheringManager#OnTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + public abstract static class OnTetheringEventCallback { + + /** + * Called when tethering upstream changed. This can be called multiple times and can be + * called any time. + * + * @param network the {@link Network} of tethering upstream. Null means tethering doesn't + * have any upstream. + */ + public void onUpstreamChanged(@Nullable Network network) {} + } + + @GuardedBy("mTetheringEventCallbacks") + private final ArrayMap + mTetheringEventCallbacks = new ArrayMap<>(); + + /** + * Start listening to tethering change events. Any new added callback will receive the last + * tethering status right away. If callback is registered when tethering has no upstream or + * disabled, {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called + * with a null argument. The same callback object cannot be registered twice. + * + * @param executor the executor on which callback will be invoked. + * @param callback the callback to be called when tethering has change events. + * + * @deprecated Use {@link TetheringManager#registerTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void registerTetheringEventCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull final OnTetheringEventCallback callback) { + Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null."); + + final TetheringEventCallback tetherCallback = + new TetheringEventCallback() { + @Override + public void onUpstreamChanged(@Nullable Network network) { + callback.onUpstreamChanged(network); + } + }; + + synchronized (mTetheringEventCallbacks) { + mTetheringEventCallbacks.put(callback, tetherCallback); + mTetheringManager.registerTetheringEventCallback(executor, tetherCallback); + } + } + + /** + * Remove tethering event callback previously registered with + * {@link #registerTetheringEventCallback}. + * + * @param callback previously registered callback. + * + * @deprecated Use {@link TetheringManager#unregisterTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void unregisterTetheringEventCallback( + @NonNull final OnTetheringEventCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + synchronized (mTetheringEventCallbacks) { + final TetheringEventCallback tetherCallback = + mTetheringEventCallbacks.remove(callback); + mTetheringManager.unregisterTetheringEventCallback(tetherCallback); + } + } + + + /** + * Get the list of regular expressions that define any tetherable + * USB network interfaces. If USB tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable usb interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableUsbRegexs() { + return mTetheringManager.getTetherableUsbRegexs(); + } + + /** + * Get the list of regular expressions that define any tetherable + * Wifi network interfaces. If Wifi tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable wifi interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableWifiRegexs() { + return mTetheringManager.getTetherableWifiRegexs(); + } + + /** + * Get the list of regular expressions that define any tetherable + * Bluetooth network interfaces. If Bluetooth tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable bluetooth interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged( + *TetheringManager.TetheringInterfaceRegexps)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableBluetoothRegexs() { + return mTetheringManager.getTetherableBluetoothRegexs(); + } + + /** + * Attempt to both alter the mode of USB and Tethering of USB. A + * utility method to deal with some of the complexity of USB - will + * attempt to switch to Rndis and subsequently tether the resulting + * interface on {@code true} or turn off tethering and switch off + * Rndis on {@code false}. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @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 mTetheringManager.setUsbTethering(enable); + } + + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_NO_ERROR}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNKNOWN_IFACE = + TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_SERVICE_UNAVAIL}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_SERVICE_UNAVAIL = + TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNSUPPORTED}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNSUPPORTED = TetheringManager.TETHER_ERROR_UNSUPPORTED; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNAVAIL_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNAVAIL_IFACE = + TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_INTERNAL_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_MASTER_ERROR = + TetheringManager.TETHER_ERROR_INTERNAL_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_TETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_TETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNTETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENABLE_FORWARDING_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_ENABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DISABLE_FORWARDING_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DISABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_IFACE_CFG_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_IFACE_CFG_ERROR = + TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_PROVISIONING_FAILED}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_PROVISION_FAILED = + TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DHCPSERVER_ERROR = + TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENTITLEMENT_UNKNOWN}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = + TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; + + /** + * Get a more detailed error code after a Tethering or Untethering + * request asynchronously failed. + * + * @param iface The name of the interface of interest + * @return error The error code of the last error tethering or untethering the named + * interface + * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public int getLastTetherError(String iface) { + int error = mTetheringManager.getLastTetherError(iface); + if (error == TetheringManager.TETHER_ERROR_UNKNOWN_TYPE) { + // TETHER_ERROR_UNKNOWN_TYPE was introduced with TetheringManager and has never been + // returned by ConnectivityManager. Convert it to the legacy TETHER_ERROR_UNKNOWN_IFACE + // instead. + error = TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; + } + return error; + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + TETHER_ERROR_NO_ERROR, + TETHER_ERROR_PROVISION_FAILED, + TETHER_ERROR_ENTITLEMENT_UNKONWN, + }) + public @interface EntitlementResultCode { + } + + /** + * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether + * entitlement succeeded. + * + * @deprecated Use {@link TetheringManager#OnTetheringEntitlementResultListener} instead. + * @hide + */ + @SystemApi + @Deprecated + public interface OnTetheringEntitlementResultListener { + /** + * Called to notify entitlement result. + * + * @param resultCode an int value of entitlement result. It may be one of + * {@link #TETHER_ERROR_NO_ERROR}, + * {@link #TETHER_ERROR_PROVISION_FAILED}, or + * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}. + */ + void onTetheringEntitlementResult(@EntitlementResultCode int resultCode); + } + + /** + * Get the last value of the entitlement check on this downstream. If the cached value is + * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the + * cached value. Otherwise, a UI-based entitlement check would be performed. It is not + * guaranteed that the UI-based entitlement check will complete in any specific time period + * and may in fact never complete. Any successful entitlement check the platform performs for + * any reason will update the cached value. + * + * @param type the downstream type of tethering. Must be one of + * {@link #TETHERING_WIFI}, + * {@link #TETHERING_USB}, or + * {@link #TETHERING_BLUETOOTH}. + * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check. + * @param executor the executor on which callback will be invoked. + * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to + * notify the caller of the result of entitlement check. The listener may be called zero + * or one time. + * @deprecated Use {@link TetheringManager#requestLatestTetheringEntitlementResult} instead. + * {@hide} + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, + @NonNull @CallbackExecutor Executor executor, + @NonNull final OnTetheringEntitlementResultListener listener) { + Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null."); + ResultReceiver wrappedListener = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> { + listener.onTetheringEntitlementResult(resultCode); + })); + } + }; + + mTetheringManager.requestLatestTetheringEntitlementResult(type, wrappedListener, + showEntitlementUi); + } + + /** + * Report network connectivity status. This is currently used only + * to alter status bar UI. + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#STATUS_BAR}. + * + * @param networkType The type of network you want to report on + * @param percentage The quality of the connection 0 is bad, 100 is good + * @deprecated Types are deprecated. Use {@link #reportNetworkConnectivity} instead. + * {@hide} + */ + public void reportInetCondition(int networkType, int percentage) { + printStackTrace(); + try { + mService.reportInetCondition(networkType, percentage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report a problem network to the framework. This provides a hint to the system + * that there might be connectivity problems on this network and may cause + * the framework to re-evaluate network connectivity and/or switch to another + * network. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both + * working and non-working connectivity. + */ + @Deprecated + public void reportBadNetwork(@Nullable Network network) { + printStackTrace(); + try { + // One of these will be ignored because it matches system's current state. + // The other will trigger the necessary reevaluation. + mService.reportNetworkConnectivity(network, true); + mService.reportNetworkConnectivity(network, false); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report to the framework whether a network has working connectivity. + * This provides a hint to the system that a particular network is providing + * working connectivity or not. In response the framework may re-evaluate + * the network's connectivity and might take further action thereafter. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @param hasConnectivity {@code true} if the application was able to successfully access the + * Internet using {@code network} or {@code false} if not. + */ + public void reportNetworkConnectivity(@Nullable Network network, boolean hasConnectivity) { + printStackTrace(); + try { + mService.reportNetworkConnectivity(network, hasConnectivity); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set a network-independent global http proxy. This is not normally what you want + * for typical HTTP proxies - they are general network dependent. However if you're + * doing something unusual like general internal filtering this may be useful. On + * a private network where the proxy is not accessible, you may break HTTP using this. + * + * @param p A {@link ProxyInfo} object defining the new global + * HTTP proxy. A {@code null} value will clear the global HTTP proxy. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setGlobalProxy(ProxyInfo p) { + try { + mService.setGlobalProxy(p); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve any network-independent global HTTP proxy. + * + * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null} + * if no global HTTP proxy is set. + * @hide + */ + public ProxyInfo getGlobalProxy() { + try { + return mService.getGlobalProxy(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve the global HTTP proxy, or if no global HTTP proxy is set, a + * network-specific HTTP proxy. If {@code network} is null, the + * network-specific proxy returned is the proxy of the default active + * network. + * + * @return {@link ProxyInfo} for the current global HTTP proxy, or if no + * global HTTP proxy is set, {@code ProxyInfo} for {@code network}, + * or when {@code network} is {@code null}, + * the {@code ProxyInfo} for the default active network. Returns + * {@code null} when no proxy applies or the caller doesn't have + * permission to use {@code network}. + * @hide + */ + public ProxyInfo getProxyForNetwork(Network network) { + try { + return mService.getProxyForNetwork(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the current default HTTP proxy settings. If a global proxy is set it will be returned, + * otherwise if this process is bound to a {@link Network} using + * {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise + * the default network's proxy is returned. + * + * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no + * HTTP proxy is active. + */ + @Nullable + public ProxyInfo getDefaultProxy() { + return getProxyForNetwork(getBoundNetworkForProcess()); + } + + /** + * Returns true if the hardware supports the given network type + * else it returns false. This doesn't indicate we have coverage + * or are authorized onto a network, just whether or not the + * hardware supports it. For example a GSM phone without a SIM + * should still return {@code true} for mobile data, but a wifi only + * tablet would return {@code false}. + * + * @param networkType The network type we'd like to check + * @return {@code true} if supported, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public boolean isNetworkSupported(int networkType) { + try { + return mService.isNetworkSupported(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns if the currently active data network is metered. A network is + * classified as metered when the user is sensitive to heavy data usage on + * that connection due to monetary costs, data limitations or + * battery/performance issues. You should check this before doing large + * data transfers, and warn the user or delay the operation until another + * network is available. + * + * @return {@code true} if large transfers should be avoided, otherwise + * {@code false}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public boolean isActiveNetworkMetered() { + try { + return mService.isActiveNetworkMetered(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * If the LockdownVpn mechanism is enabled, updates the vpn + * with a reload of its profile. + * + * @return a boolean with {@code} indicating success + * + *

This method can only be called by the system UID + * {@hide} + */ + public boolean updateLockdownVpn() { + try { + return mService.updateLockdownVpn(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set sign in error notification to visible or invisible + * + * @hide + * @deprecated Doesn't properly deal with multiple connected networks of the same type. + */ + @Deprecated + public void setProvisioningNotificationVisible(boolean visible, int networkType, + String action) { + try { + mService.setProvisioningNotificationVisible(visible, networkType, action); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set the value for enabling/disabling airplane mode + * + * @param enable whether to enable airplane mode or not + * + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_AIRPLANE_MODE, + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK}) + @SystemApi + public void setAirplaneMode(boolean enable) { + try { + mService.setAirplaneMode(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} - returns the factory serial number */ + @UnsupportedAppUsage + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public int registerNetworkFactory(Messenger messenger, String name) { + try { + return mService.registerNetworkFactory(messenger, name); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void unregisterNetworkFactory(Messenger messenger) { + try { + mService.unregisterNetworkFactory(messenger); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers the specified {@link NetworkProvider}. + * Each listener must only be registered once. The listener can be unregistered with + * {@link #unregisterNetworkProvider}. + * + * @param provider the provider to register + * @return the ID of the provider. This ID must be used by the provider when registering + * {@link android.net.NetworkAgent}s. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public int registerNetworkProvider(@NonNull NetworkProvider provider) { + if (provider.getProviderId() != NetworkProvider.ID_NONE) { + throw new IllegalStateException("NetworkProviders can only be registered once"); + } + + try { + int providerId = mService.registerNetworkProvider(provider.getMessenger(), + provider.getName()); + provider.setProviderId(providerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return provider.getProviderId(); + } + + /** + * Unregisters the specified NetworkProvider. + * + * @param provider the provider to unregister + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void unregisterNetworkProvider(@NonNull NetworkProvider provider) { + try { + mService.unregisterNetworkProvider(provider.getMessenger()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + provider.setProviderId(NetworkProvider.ID_NONE); + } + + + /** @hide exposed via the NetworkProvider class. */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { + try { + mService.declareNetworkRequestUnfulfillable(request); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + // TODO : remove this method. It is a stopgap measure to help sheperding a number + // of dependent changes that would conflict throughout the automerger graph. Having this + // temporarily helps with the process of going through with all these dependent changes across + // the entire tree. + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, + NetworkCapabilities nc, int score, NetworkAgentConfig config) { + return registerNetworkAgent(na, ni, lp, nc, score, config, NetworkProvider.ID_NONE); + } + + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, + NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { + try { + return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Base class for {@code NetworkRequest} callbacks. Used for notifications about network + * changes. Should be extended by applications wanting notifications. + * + * A {@code NetworkCallback} is registered by calling + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)}, + * or {@link #registerDefaultNetworkCallback(NetworkCallback)}. A {@code NetworkCallback} is + * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}. + * A {@code NetworkCallback} should be registered at most once at any time. + * A {@code NetworkCallback} that has been unregistered can be registered again. + */ + public static class NetworkCallback { + /** + * Called when the framework connects to a new network to evaluate whether it satisfies this + * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} + * callback. There is no guarantee that this new network will satisfy any requests, or that + * the network will stay connected for longer than the time necessary to evaluate it. + *

+ * Most applications should not act on this callback, and should instead use + * {@link #onAvailable}. This callback is intended for use by applications that can assist + * the framework in properly evaluating the network — for example, an application that + * can automatically log in to a captive portal without user intervention. + * + * @param network The {@link Network} of the network that is being evaluated. + * + * @hide + */ + public void onPreCheck(@NonNull Network network) {} + + /** + * Called when the framework connects and has declared a new network ready for use. + * This callback may be called more than once if the {@link Network} that is + * satisfying the request changes. + * + * @param network The {@link Network} of the satisfying network. + * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. + * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @param blocked Whether access to the {@link Network} is blocked due to system policy. + * @hide + */ + public void onAvailable(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, boolean blocked) { + // Internally only this method is called when a new network is available, and + // it calls the callback in the same way and order that older versions used + // to call so as not to change the behavior. + onAvailable(network); + if (!networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) { + onNetworkSuspended(network); + } + onCapabilitiesChanged(network, networkCapabilities); + onLinkPropertiesChanged(network, linkProperties); + onBlockedStatusChanged(network, blocked); + } + + /** + * Called when the framework connects and has declared a new network ready for use. + * + *

For callbacks registered with {@link #registerNetworkCallback}, multiple networks may + * be available at the same time, and onAvailable will be called for each of these as they + * appear. + * + *

For callbacks registered with {@link #requestNetwork} and + * {@link #registerDefaultNetworkCallback}, this means the network passed as an argument + * is the new best network for this request and is now tracked by this callback ; this + * callback will no longer receive method calls about other networks that may have been + * passed to this method previously. The previously-best network may have disconnected, or + * it may still be around and the newly-best network may simply be better. + * + *

Starting with {@link android.os.Build.VERSION_CODES#O}, this will always immediately + * be followed by a call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} + * then by a call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call + * to {@link #onBlockedStatusChanged(Network, boolean)}. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions (there is no guarantee the objects + * returned by these methods will be current). Instead, wait for a call to + * {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link #onLinkPropertiesChanged(Network, LinkProperties)} whose arguments are guaranteed + * to be well-ordered with respect to other callbacks. + * + * @param network The {@link Network} of the satisfying network. + */ + public void onAvailable(@NonNull Network network) {} + + /** + * Called when the network is about to be lost, typically because there are no outstanding + * requests left for it. This may be paired with a {@link NetworkCallback#onAvailable} call + * with the new replacement network for graceful handover. This method is not guaranteed + * to be called before {@link NetworkCallback#onLost} is called, for example in case a + * network is suddenly disconnected. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions ; calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} that is about to be lost. + * @param maxMsToLive The time in milliseconds the system intends to keep the network + * connected for graceful handover; note that the network may still + * suffer a hard loss at any time. + */ + public void onLosing(@NonNull Network network, int maxMsToLive) {} + + /** + * Called when a network disconnects or otherwise no longer satisfies this request or + * callback. + * + *

If the callback was registered with requestNetwork() or + * registerDefaultNetworkCallback(), it will only be invoked against the last network + * returned by onAvailable() when that network is lost and no other network satisfies + * the criteria of the request. + * + *

If the callback was registered with registerNetworkCallback() it will be called for + * each network which no longer satisfies the criteria of the callback. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions ; calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} lost. + */ + public void onLost(@NonNull Network network) {} + + /** + * Called if no network is found within the timeout time specified in + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the + * requested network request cannot be fulfilled (whether or not a timeout was + * specified). When this callback is invoked the associated + * {@link NetworkRequest} will have already been removed and released, as if + * {@link #unregisterNetworkCallback(NetworkCallback)} had been called. + */ + public void onUnavailable() {} + + /** + * Called when the network corresponding to this request changes capabilities but still + * satisfies the requested criteria. + * + *

Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed + * to be called immediately after {@link #onAvailable}. + * + *

Do NOT call {@link #getLinkProperties(Network)} or other synchronous + * ConnectivityManager methods in this callback as this is prone to race conditions : + * calling these methods while in a callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose capabilities have changed. + * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this + * network. + */ + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities) {} + + /** + * Called when the network corresponding to this request changes {@link LinkProperties}. + * + *

Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed + * to be called immediately after {@link #onAvailable}. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or other synchronous + * ConnectivityManager methods in this callback as this is prone to race conditions : + * calling these methods while in a callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose link properties have changed. + * @param linkProperties The new {@link LinkProperties} for this network. + */ + public void onLinkPropertiesChanged(@NonNull Network network, + @NonNull LinkProperties linkProperties) {} + + /** + * Called when the network the framework connected to for this request suspends data + * transmission temporarily. + * + *

This generally means that while the TCP connections are still live temporarily + * network data fails to transfer. To give a specific example, this is used on cellular + * networks to mask temporary outages when driving through a tunnel, etc. In general this + * means read operations on sockets on this network will block once the buffers are + * drained, and write operations will block once the buffers are full. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions (there is no guarantee the objects + * returned by these methods will be current). + * + * @hide + */ + public void onNetworkSuspended(@NonNull Network network) {} + + /** + * Called when the network the framework connected to for this request + * returns from a {@link NetworkInfo.State#SUSPENDED} state. This should always be + * preceded by a matching {@link NetworkCallback#onNetworkSuspended} call. + + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @hide + */ + public void onNetworkResumed(@NonNull Network network) {} + + /** + * Called when access to the specified network is blocked or unblocked. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose blocked status has changed. + * @param blocked The blocked status of this {@link Network}. + */ + public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {} + + private NetworkRequest networkRequest; + } + + /** + * Constant error codes used by ConnectivityService to communicate about failures and errors + * across a Binder boundary. + * @hide + */ + public interface Errors { + int TOO_MANY_REQUESTS = 1; + } + + /** @hide */ + public static class TooManyRequestsException extends RuntimeException {} + + private static RuntimeException convertServiceException(ServiceSpecificException e) { + switch (e.errorCode) { + case Errors.TOO_MANY_REQUESTS: + return new TooManyRequestsException(); + default: + Log.w(TAG, "Unknown service error code " + e.errorCode); + return new RuntimeException(e); + } + } + + private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER; + /** @hide */ + public static final int CALLBACK_PRECHECK = BASE + 1; + /** @hide */ + public static final int CALLBACK_AVAILABLE = BASE + 2; + /** @hide arg1 = TTL */ + public static final int CALLBACK_LOSING = BASE + 3; + /** @hide */ + public static final int CALLBACK_LOST = BASE + 4; + /** @hide */ + public static final int CALLBACK_UNAVAIL = BASE + 5; + /** @hide */ + public static final int CALLBACK_CAP_CHANGED = BASE + 6; + /** @hide */ + public static final int CALLBACK_IP_CHANGED = BASE + 7; + /** @hide obj = NetworkCapabilities, arg1 = seq number */ + private static final int EXPIRE_LEGACY_REQUEST = BASE + 8; + /** @hide */ + public static final int CALLBACK_SUSPENDED = BASE + 9; + /** @hide */ + public static final int CALLBACK_RESUMED = BASE + 10; + /** @hide */ + public static final int CALLBACK_BLK_CHANGED = BASE + 11; + + /** @hide */ + public static String getCallbackName(int whichCallback) { + switch (whichCallback) { + case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK"; + case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE"; + case CALLBACK_LOSING: return "CALLBACK_LOSING"; + case CALLBACK_LOST: return "CALLBACK_LOST"; + case CALLBACK_UNAVAIL: return "CALLBACK_UNAVAIL"; + case CALLBACK_CAP_CHANGED: return "CALLBACK_CAP_CHANGED"; + case CALLBACK_IP_CHANGED: return "CALLBACK_IP_CHANGED"; + case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST"; + case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED"; + case CALLBACK_RESUMED: return "CALLBACK_RESUMED"; + case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED"; + default: + return Integer.toString(whichCallback); + } + } + + private class CallbackHandler extends Handler { + private static final String TAG = "ConnectivityManager.CallbackHandler"; + private static final boolean DBG = false; + + CallbackHandler(Looper looper) { + super(looper); + } + + CallbackHandler(Handler handler) { + this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper()); + } + + @Override + public void handleMessage(Message message) { + if (message.what == EXPIRE_LEGACY_REQUEST) { + expireRequest((NetworkCapabilities) message.obj, message.arg1); + return; + } + + final NetworkRequest request = getObject(message, NetworkRequest.class); + final Network network = getObject(message, Network.class); + final NetworkCallback callback; + synchronized (sCallbacks) { + callback = sCallbacks.get(request); + if (callback == null) { + Log.w(TAG, + "callback not found for " + getCallbackName(message.what) + " message"); + return; + } + if (message.what == CALLBACK_UNAVAIL) { + sCallbacks.remove(request); + callback.networkRequest = ALREADY_UNREGISTERED; + } + } + if (DBG) { + Log.d(TAG, getCallbackName(message.what) + " for network " + network); + } + + switch (message.what) { + case CALLBACK_PRECHECK: { + callback.onPreCheck(network); + break; + } + case CALLBACK_AVAILABLE: { + NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); + LinkProperties lp = getObject(message, LinkProperties.class); + callback.onAvailable(network, cap, lp, message.arg1 != 0); + break; + } + case CALLBACK_LOSING: { + callback.onLosing(network, message.arg1); + break; + } + case CALLBACK_LOST: { + callback.onLost(network); + break; + } + case CALLBACK_UNAVAIL: { + callback.onUnavailable(); + break; + } + case CALLBACK_CAP_CHANGED: { + NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); + callback.onCapabilitiesChanged(network, cap); + break; + } + case CALLBACK_IP_CHANGED: { + LinkProperties lp = getObject(message, LinkProperties.class); + callback.onLinkPropertiesChanged(network, lp); + break; + } + case CALLBACK_SUSPENDED: { + callback.onNetworkSuspended(network); + break; + } + case CALLBACK_RESUMED: { + callback.onNetworkResumed(network); + break; + } + case CALLBACK_BLK_CHANGED: { + boolean blocked = message.arg1 != 0; + callback.onBlockedStatusChanged(network, blocked); + } + } + } + + private T getObject(Message msg, Class c) { + return (T) msg.getData().getParcelable(c.getSimpleName()); + } + } + + private CallbackHandler getDefaultHandler() { + synchronized (sCallbacks) { + if (sCallbackHandler == null) { + sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper()); + } + return sCallbackHandler; + } + } + + private static final HashMap sCallbacks = new HashMap<>(); + private static CallbackHandler sCallbackHandler; + + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, + int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + printStackTrace(); + checkCallbackNotNull(callback); + Preconditions.checkArgument( + reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities"); + final NetworkRequest request; + final String callingPackageName = mContext.getOpPackageName(); + try { + synchronized(sCallbacks) { + if (callback.networkRequest != null + && callback.networkRequest != ALREADY_UNREGISTERED) { + // TODO: throw exception instead and enforce 1:1 mapping of callbacks + // and requests (http://b/20701525). + Log.e(TAG, "NetworkCallback was already registered"); + } + Messenger messenger = new Messenger(handler); + Binder binder = new Binder(); + if (reqType == LISTEN) { + request = mService.listenForNetwork( + need, messenger, binder, callingPackageName); + } else { + request = mService.requestNetwork( + need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, + callingPackageName, getAttributionTag()); + } + if (request != null) { + sCallbacks.put(request, callback); + } + callback.networkRequest = request; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + return request; + } + + /** + * Helper function to request a network with a particular legacy type. + * + * This API is only for use in internal system code that requests networks with legacy type and + * relies on CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use + * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead. + * + * @param request {@link NetworkRequest} describing this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + * @param legacyType to specify the network type(#TYPE_*). + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void requestNetwork(@NonNull NetworkRequest request, + int timeoutMs, int legacyType, @NonNull Handler handler, + @NonNull NetworkCallback networkCallback) { + if (legacyType == TYPE_NONE) { + throw new IllegalArgumentException("TYPE_NONE is meaningless legacy type"); + } + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + *

This method will attempt to find the best network that matches the passed + * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the + * criteria. The platform will evaluate which network is the best at its own discretion. + * Throughput, latency, cost per byte, policy, user preference and other considerations + * may be factored in the decision of what is considered the best network. + * + *

As long as this request is outstanding, the platform will try to maintain the best network + * matching this request, while always attempting to match the request to a better network if + * possible. If a better match is found, the platform will switch this request to the now-best + * network and inform the app of the newly best network by invoking + * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform + * will not try to maintain any other network than the best one currently matching the request: + * a network not matching any network request may be disconnected at any time. + * + *

For example, an application could use this method to obtain a connected cellular network + * even if the device currently has a data connection over Ethernet. This may cause the cellular + * radio to consume additional power. Or, an application could inform the system that it wants + * a network supporting sending MMSes and have the system let it know about the currently best + * MMS-supporting network through the provided {@link NetworkCallback}. + * + *

The status of the request can be followed by listening to the various callbacks described + * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be + * used to direct traffic to the network (although accessing some networks may be subject to + * holding specific permissions). Callers will learn about the specific characteristics of the + * network through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the + * provided {@link NetworkCallback} will only be invoked due to changes in the best network + * matching the request at any given time; therefore when a better network matching the request + * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called + * with the new network after which no further updates are given about the previously-best + * network, unless it becomes the best again at some later time. All callbacks are invoked + * in order on the same thread, which by default is a thread created by the framework running + * in the app. + * {@see #requestNetwork(NetworkRequest, NetworkCallback, Handler)} to change where the + * callbacks are invoked. + * + *

This{@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at + * which point the system may let go of the network at any time. + * + *

A version of this method which takes a timeout is + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}, that an app can use to only + * wait for a limited amount of time for the network to become unavailable. + * + *

It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * The callback is invoked on the default internal Handler. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback) { + requestNetwork(request, networkCallback, getDefaultHandler()); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + * This method behaves identically to {@link #requestNetwork(NetworkRequest, NetworkCallback)} + * but runs all the callbacks on the passed Handler. + * + *

This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * by a timeout. + * + * This function behaves identically to the non-timed-out version + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network + * is not found within the given time (in milliseconds) the + * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + *

Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. + * + *

This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, int timeoutMs) { + checkTimeout(timeoutMs); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE, + getDefaultHandler()); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * by a timeout. + * + * This method behaves identically to + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} but runs all the callbacks + * on the passed Handler. + * + *

This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable} is called. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) { + checkTimeout(timeoutMs); + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE, cbHandler); + } + + /** + * The lookup key for a {@link Network} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + *

+ * Note that if you intend to invoke {@link Network#openConnection(java.net.URL)} + * then you must get a ConnectivityManager instance before doing so. + */ + public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; + + /** + * The lookup key for a {@link NetworkRequest} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; + + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

+ * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

+ * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. It is important to create a new, + * {@link NetworkCallback} based request before completing the processing of the + * Intent to reserve the network or it will be released shortly after the Intent + * is processed. + *

+ * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

+ * The request may be released normally by calling + * {@link #releaseNetworkRequest(android.app.PendingIntent)}. + *

It is presently unsupported to request a network with either + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.pendingRequestForNetwork( + request.networkCapabilities, operation, mContext.getOpPackageName(), + getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + } + + /** + * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} + *

+ * This method has the same behavior as + * {@link #unregisterNetworkCallback(android.app.PendingIntent)} with respect to + * releasing network resources and disconnecting. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the + * corresponding NetworkRequest you'd like to remove. Cannot be null. + */ + public void releaseNetworkRequest(@NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.releasePendingNetworkRequest(operation); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static void checkPendingIntentNotNull(PendingIntent intent) { + Preconditions.checkNotNull(intent, "PendingIntent cannot be null."); + } + + private static void checkCallbackNotNull(NetworkCallback callback) { + Preconditions.checkNotNull(callback, "null NetworkCallback"); + } + + private static void checkTimeout(int timeoutMs) { + Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive."); + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback) { + registerNetworkCallback(request, networkCallback, getDefaultHandler()); + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler); + } + + /** + * Registers a PendingIntent to be sent when a network is available which satisfies the given + * {@link NetworkRequest}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

+ * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

+ * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. + *

+ * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

+ * The request may be released normally by calling + * {@link #unregisterNetworkCallback(android.app.PendingIntent)}. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.pendingListenForNetwork( + request.networkCapabilities, operation, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + } + + /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) { + registerDefaultNetworkCallback(networkCallback, getDefaultHandler()); + } + + /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, + @NonNull Handler handler) { + // This works because if the NetworkCapabilities are null, + // ConnectivityService takes them from the default request. + // + // Since the capabilities are exactly the same as the default request's + // capabilities, this request is guaranteed, at all times, to be + // satisfied by the same network, if any, that satisfies the default + // request, i.e., the system default network. + CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + TRACK_DEFAULT, TYPE_NONE, cbHandler); + } + + /** + * Requests bandwidth update for a given {@link Network} and returns whether the update request + * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying + * network connection for updated bandwidth information. The caller will be notified via + * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this + * method assumes that the caller has previously called + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} to listen for network + * changes. + * + * @param network {@link Network} specifying which network you're interested. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean requestBandwidthUpdate(@NonNull Network network) { + try { + return mService.requestBandwidthUpdate(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a {@code NetworkCallback} and possibly releases networks originating from + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls. + * If the given {@code NetworkCallback} had previously been used with + * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request + * will be disconnected. + * + * Notifications that would have triggered that {@code NetworkCallback} will immediately stop + * triggering it as soon as this call returns. + * + * @param networkCallback The {@link NetworkCallback} used when making the request. + */ + public void unregisterNetworkCallback(@NonNull NetworkCallback networkCallback) { + printStackTrace(); + checkCallbackNotNull(networkCallback); + final List reqs = new ArrayList<>(); + // Find all requests associated to this callback and stop callback triggers immediately. + // Callback is reusable immediately. http://b/20701525, http://b/35921499. + synchronized (sCallbacks) { + Preconditions.checkArgument(networkCallback.networkRequest != null, + "NetworkCallback was not registered"); + if (networkCallback.networkRequest == ALREADY_UNREGISTERED) { + Log.d(TAG, "NetworkCallback was already unregistered"); + return; + } + for (Map.Entry e : sCallbacks.entrySet()) { + if (e.getValue() == networkCallback) { + reqs.add(e.getKey()); + } + } + // TODO: throw exception if callback was registered more than once (http://b/20701525). + for (NetworkRequest r : reqs) { + try { + mService.releaseNetworkRequest(r); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + // Only remove mapping if rpc was successful. + sCallbacks.remove(r); + } + networkCallback.networkRequest = ALREADY_UNREGISTERED; + } + } + + /** + * Unregisters a callback previously registered via + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * Cannot be null. + */ + public void unregisterNetworkCallback(@NonNull PendingIntent operation) { + releaseNetworkRequest(operation); + } + + /** + * Informs the system whether it should switch to {@code network} regardless of whether it is + * validated or not. If {@code accept} is true, and the network was explicitly selected by the + * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become + * the system default network regardless of any other network that's currently connected. If + * {@code always} is true, then the choice is remembered, so that the next time the user + * connects to this network, the system will switch to it. + * + * @param network The network to accept. + * @param accept Whether to accept the network even if unvalidated. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { + try { + mService.setAcceptUnvalidated(network, accept, always); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs the system whether it should consider the network as validated even if it only has + * partial connectivity. If {@code accept} is true, then the network will be considered as + * validated even if connectivity is only partial. If {@code always} is true, then the choice + * is remembered, so that the next time the user connects to this network, the system will + * switch to it. + * + * @param network The network to accept. + * @param accept Whether to consider the network as validated even if it has partial + * connectivity. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) { + try { + mService.setAcceptPartialConnectivity(network, accept, always); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is + * only meaningful if the system is configured not to penalize such networks, e.g., if the + * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code + * NETWORK_AVOID_BAD_WIFI setting is unset}. + * + * @param network The network to accept. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setAvoidUnvalidated(Network network) { + try { + mService.setAvoidUnvalidated(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests that the system open the captive portal app on the specified network. + * + * @param network The network to log into. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void startCaptivePortalApp(Network network) { + try { + mService.startCaptivePortalApp(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests that the system open the captive portal app with the specified extras. + * + *

This endpoint is exclusively for use by the NetworkStack and is protected by the + * corresponding permission. + * @param network Network on which the captive portal was detected. + * @param appExtras Extras to include in the app start intent. + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void startCaptivePortalApp(@NonNull Network network, @NonNull Bundle appExtras) { + try { + mService.startCaptivePortalAppInternal(network, appExtras); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Determine whether the device is configured to avoid bad wifi. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public boolean shouldAvoidBadWifi() { + try { + return mService.shouldAvoidBadWifi(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * It is acceptable to briefly use multipath data to provide seamless connectivity for + * time-sensitive user-facing operations when the system default network is temporarily + * unresponsive. The amount of data should be limited (less than one megabyte for every call to + * this method), and the operation should be infrequent to ensure that data usage is limited. + * + * An example of such an operation might be a time-sensitive foreground activity, such as a + * voice command, that the user is performing while walking out of range of a Wi-Fi network. + */ + public static final int MULTIPATH_PREFERENCE_HANDOVER = 1 << 0; + + /** + * It is acceptable to use small amounts of multipath data on an ongoing basis to provide + * a backup channel for traffic that is primarily going over another network. + * + * An example might be maintaining backup connections to peers or servers for the purpose of + * fast fallback if the default network is temporarily unresponsive or disconnects. The traffic + * on backup paths should be negligible compared to the traffic on the main path. + */ + public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1; + + /** + * It is acceptable to use metered data to improve network latency and performance. + */ + public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2; + + /** + * Return value to use for unmetered networks. On such networks we currently set all the flags + * to true. + * @hide + */ + public static final int MULTIPATH_PREFERENCE_UNMETERED = + MULTIPATH_PREFERENCE_HANDOVER | + MULTIPATH_PREFERENCE_RELIABILITY | + MULTIPATH_PREFERENCE_PERFORMANCE; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = { + MULTIPATH_PREFERENCE_HANDOVER, + MULTIPATH_PREFERENCE_RELIABILITY, + MULTIPATH_PREFERENCE_PERFORMANCE, + }) + public @interface MultipathPreference { + } + + /** + * Provides a hint to the calling application on whether it is desirable to use the + * multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.) + * for multipath data transfer on this network when it is not the system default network. + * Applications desiring to use multipath network protocols should call this method before + * each such operation. + * + * @param network The network on which the application desires to use multipath data. + * If {@code null}, this method will return the a preference that will generally + * apply to metered networks. + * @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public @MultipathPreference int getMultipathPreference(@Nullable Network network) { + try { + return mService.getMultipathPreference(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resets all connectivity manager settings back to factory defaults. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void factoryReset() { + try { + mService.factoryReset(); + mTetheringManager.stopAllTethering(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code bindProcessToNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean bindProcessToNetwork(@Nullable Network network) { + // Forcing callers to call through non-static function ensures ConnectivityManager + // instantiated. + return setProcessDefaultNetwork(network); + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code setProcessDefaultNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @deprecated This function can throw {@link IllegalStateException}. Use + * {@link #bindProcessToNetwork} instead. {@code bindProcessToNetwork} + * is a direct replacement. + */ + @Deprecated + public static boolean setProcessDefaultNetwork(@Nullable Network network) { + int netId = (network == null) ? NETID_UNSET : network.netId; + boolean isSameNetId = (netId == NetworkUtils.getBoundNetworkForProcess()); + + if (netId != NETID_UNSET) { + netId = network.getNetIdForResolv(); + } + + if (!NetworkUtils.bindProcessToNetwork(netId)) { + return false; + } + + if (!isSameNetId) { + // Set HTTP proxy system properties to match network. + // TODO: Deprecate this static method and replace it with a non-static version. + try { + Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy()); + } catch (SecurityException e) { + // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy. + Log.e(TAG, "Can't set proxy properties", e); + } + // Must flush DNS cache as new network may have different DNS resolutions. + InetAddress.clearDnsCache(); + // Must flush socket pool as idle sockets will be bound to previous network and may + // cause subsequent fetches to be performed on old network. + NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); + } + + return true; + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + */ + @Nullable + public Network getBoundNetworkForProcess() { + // Forcing callers to call thru non-static function ensures ConnectivityManager + // instantiated. + return getProcessDefaultNetwork(); + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + * @deprecated Using this function can lead to other functions throwing + * {@link IllegalStateException}. Use {@link #getBoundNetworkForProcess} instead. + * {@code getBoundNetworkForProcess} is a direct replacement. + */ + @Deprecated + @Nullable + public static Network getProcessDefaultNetwork() { + int netId = NetworkUtils.getBoundNetworkForProcess(); + if (netId == NETID_UNSET) return null; + return new Network(netId); + } + + private void unsupportedStartingFrom(int version) { + if (Process.myUid() == Process.SYSTEM_UID) { + // The getApplicationInfo() call we make below is not supported in system context. Let + // the call through here, and rely on the fact that ConnectivityService will refuse to + // allow the system to use these APIs anyway. + return; + } + + if (mContext.getApplicationInfo().targetSdkVersion >= version) { + throw new UnsupportedOperationException( + "This method is not supported in target SDK version " + version + " and above"); + } + } + + // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature, + // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException. + // TODO: convert the existing system users (Tethering, GnssLocationProvider) to the new APIs and + // remove these exemptions. Note that this check is not secure, and apps can still access these + // functions by accessing ConnectivityService directly. However, it should be clear that doing + // so is unsupported and may break in the future. http://b/22728205 + private void checkLegacyRoutingApiAccess() { + unsupportedStartingFrom(VERSION_CODES.M); + } + + /** + * Binds host resolutions performed by this process to {@code network}. + * {@link #bindProcessToNetwork} takes precedence over this setting. + * + * @param network The {@link Network} to bind host resolutions from the current process to, or + * {@code null} to clear the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @hide + * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}. + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static boolean setProcessDefaultNetworkForHostResolution(Network network) { + return NetworkUtils.bindProcessToNetworkForHostResolution( + (network == null) ? NETID_UNSET : network.getNetIdForResolv()); + } + + /** + * Device is not restricting metered network activity while application is running on + * background. + */ + public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; + + /** + * Device is restricting metered network activity while application is running on background, + * but application is allowed to bypass it. + *

+ * In this state, application should take action to mitigate metered network access. + * For example, a music streaming application should switch to a low-bandwidth bitrate. + */ + public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; + + /** + * Device is restricting metered network activity while application is running on background. + *

+ * In this state, application should not try to use the network while running on background, + * because it would be denied. + */ + public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; + + /** + * A change in the background metered network activity restriction has occurred. + *

+ * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction + * applies to them. + *

+ * This is only sent to registered receivers, not manifest receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = + "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, value = { + RESTRICT_BACKGROUND_STATUS_DISABLED, + RESTRICT_BACKGROUND_STATUS_WHITELISTED, + RESTRICT_BACKGROUND_STATUS_ENABLED, + }) + public @interface RestrictBackgroundStatus { + } + + private INetworkPolicyManager getNetworkPolicyManager() { + synchronized (this) { + if (mNPManager != null) { + return mNPManager; + } + mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager + .getService(Context.NETWORK_POLICY_SERVICE)); + return mNPManager; + } + } + + /** + * Determines if the calling application is subject to metered network restrictions while + * running on background. + * + * @return {@link #RESTRICT_BACKGROUND_STATUS_DISABLED}, + * {@link #RESTRICT_BACKGROUND_STATUS_ENABLED}, + * or {@link #RESTRICT_BACKGROUND_STATUS_WHITELISTED} + */ + public @RestrictBackgroundStatus int getRestrictBackgroundStatus() { + try { + return getNetworkPolicyManager().getRestrictBackgroundByCaller(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * The network watchlist is a list of domains and IP addresses that are associated with + * potentially harmful apps. This method returns the SHA-256 of the watchlist config file + * currently used by the system for validation purposes. + * + * @return Hash of network watchlist config file. Null if config does not exist. + */ + @Nullable + public byte[] getNetworkWatchlistConfigHash() { + try { + return mService.getNetworkWatchlistConfigHash(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get watchlist config hash"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the {@code uid} of the owner of a network connection. + * + * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and {@code + * IPPROTO_UDP} currently supported. + * @param local The local {@link InetSocketAddress} of a connection. + * @param remote The remote {@link InetSocketAddress} of a connection. + * @return {@code uid} if the connection is found and the app has permission to observe it + * (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link + * android.os.Process#INVALID_UID} if the connection is not found. + * @throws {@link SecurityException} if the caller is not the active VpnService for the current + * user. + * @throws {@link IllegalArgumentException} if an unsupported protocol is requested. + */ + public int getConnectionOwnerUid( + int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) { + ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote); + try { + return mService.getConnectionOwnerUid(connectionInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void printStackTrace() { + if (DEBUG) { + final StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); + final StringBuffer sb = new StringBuffer(); + for (int i = 3; i < callStack.length; i++) { + final String stackTrace = callStack[i].toString(); + if (stackTrace == null || stackTrace.contains("android.os")) { + break; + } + sb.append(" [").append(stackTrace).append("]"); + } + Log.d(TAG, "StackLog:" + sb.toString()); + } + } + + /** + * Simulates a Data Stall for the specified Network. + * + *

This method should only be used for tests. + * + *

The caller must be the owner of the specified Network. + * + * @param detectionMethod The detection method used to identify the Data Stall. + * @param timestampMillis The timestamp at which the stall 'occurred', in milliseconds. + * @param network The Network for which a Data Stall is being simluated. + * @param extras The PersistableBundle of extras included in the Data Stall notification. + * @throws SecurityException if the caller is not the owner of the given network. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_TEST_NETWORKS, + android.Manifest.permission.NETWORK_STACK}) + public void simulateDataStall(int detectionMethod, long timestampMillis, + @NonNull Network network, @NonNull PersistableBundle extras) { + try { + mService.simulateDataStall(detectionMethod, timestampMillis, network, extras); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + private void setOemNetworkPreference(@NonNull OemNetworkPreferences preference) { + Log.d(TAG, "setOemNetworkPreference called with preference: " + + preference.toString()); + } + + @NonNull + private final List mQosCallbackConnections = new ArrayList<>(); + + /** + * Registers a {@link QosSocketInfo} with an associated {@link QosCallback}. The callback will + * receive available QoS events related to the {@link Network} and local ip + port + * specified within socketInfo. + *

+ * The same {@link QosCallback} must be unregistered before being registered a second time, + * otherwise {@link QosCallbackRegistrationException} is thrown. + *

+ * This API does not, in itself, require any permission if called with a network that is not + * restricted. However, the underlying implementation currently only supports the IMS network, + * which is always restricted. That means non-preinstalled callers can't possibly find this API + * useful, because they'd never be called back on networks that they would have access to. + * + * @throws SecurityException if {@link QosSocketInfo#getNetwork()} is restricted and the app is + * missing CONNECTIVITY_USE_RESTRICTED_NETWORKS permission. + * @throws QosCallback.QosCallbackRegistrationException if qosCallback is already registered. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * Exceptions after the time of registration is passed through + * {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}. + * + * @param socketInfo the socket information used to match QoS events + * @param callback receives qos events that satisfy socketInfo + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed. + * + * @hide + */ + @SystemApi + public void registerQosCallback(@NonNull final QosSocketInfo socketInfo, + @NonNull final QosCallback callback, + @CallbackExecutor @NonNull final Executor executor) { + Objects.requireNonNull(socketInfo, "socketInfo must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); + Objects.requireNonNull(executor, "executor must be non-null"); + + try { + synchronized (mQosCallbackConnections) { + if (getQosCallbackConnection(callback) == null) { + final QosCallbackConnection connection = + new QosCallbackConnection(this, callback, executor); + mQosCallbackConnections.add(connection); + mService.registerQosSocketCallback(socketInfo, connection); + } else { + Log.e(TAG, "registerQosCallback: Callback already registered"); + throw new QosCallbackRegistrationException(); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + + // The same unregister method method is called for consistency even though nothing + // will be sent to the ConnectivityService since the callback was never successfully + // registered. + unregisterQosCallback(callback); + e.rethrowFromSystemServer(); + } catch (final ServiceSpecificException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + unregisterQosCallback(callback); + throw convertServiceException(e); + } + } + + /** + * Unregisters the given {@link QosCallback}. The {@link QosCallback} will no longer receive + * events once unregistered and can be registered a second time. + *

+ * If the {@link QosCallback} does not have an active registration, it is a no-op. + * + * @param callback the callback being unregistered + * + * @hide + */ + @SystemApi + public void unregisterQosCallback(@NonNull final QosCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + try { + synchronized (mQosCallbackConnections) { + final QosCallbackConnection connection = getQosCallbackConnection(callback); + if (connection != null) { + connection.stopReceivingMessages(); + mService.unregisterQosCallback(connection); + mQosCallbackConnections.remove(connection); + } else { + Log.d(TAG, "unregisterQosCallback: Callback not registered"); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "unregisterQosCallback: Error while unregistering ", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Gets the connection related to the callback. + * + * @param callback the callback to look up + * @return the related connection + */ + @Nullable + private QosCallbackConnection getQosCallbackConnection(final QosCallback callback) { + for (final QosCallbackConnection connection : mQosCallbackConnections) { + // Checking by reference here is intentional + if (connection.getCallback() == callback) { + return connection; + } + } + return null; + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, but + * does not cause any networks to retain the NET_CAPABILITY_FOREGROUND capability. This can + * be used to request that the system provide a network without causing the network to be + * in the foreground. + * + *

This method will attempt to find the best network that matches the passed + * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the + * criteria. The platform will evaluate which network is the best at its own discretion. + * Throughput, latency, cost per byte, policy, user preference and other considerations + * may be factored in the decision of what is considered the best network. + * + *

As long as this request is outstanding, the platform will try to maintain the best network + * matching this request, while always attempting to match the request to a better network if + * possible. If a better match is found, the platform will switch this request to the now-best + * network and inform the app of the newly best network by invoking + * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform + * will not try to maintain any other network than the best one currently matching the request: + * a network not matching any network request may be disconnected at any time. + * + *

For example, an application could use this method to obtain a connected cellular network + * even if the device currently has a data connection over Ethernet. This may cause the cellular + * radio to consume additional power. Or, an application could inform the system that it wants + * a network supporting sending MMSes and have the system let it know about the currently best + * MMS-supporting network through the provided {@link NetworkCallback}. + * + *

The status of the request can be followed by listening to the various callbacks described + * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be + * used to direct traffic to the network (although accessing some networks may be subject to + * holding specific permissions). Callers will learn about the specific characteristics of the + * network through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the + * provided {@link NetworkCallback} will only be invoked due to changes in the best network + * matching the request at any given time; therefore when a better network matching the request + * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called + * with the new network after which no further updates are given about the previously-best + * network, unless it becomes the best again at some later time. All callbacks are invoked + * in order on the same thread, which by default is a thread created by the framework running + * in the app. + * + *

This{@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at + * which point the system may let go of the network at any time. + * + *

It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * If null, the callback is invoked on the default internal Handler. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint("ExecutorRegistration") + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void requestBackgroundNetwork(@NonNull NetworkRequest request, + @Nullable Handler handler, @NonNull NetworkCallback networkCallback) { + final NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, + TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler)); + } +} diff --git a/framework/src/android/net/ConnectivityMetricsEvent.aidl b/framework/src/android/net/ConnectivityMetricsEvent.aidl new file mode 100644 index 0000000000..1c541dc4c8 --- /dev/null +++ b/framework/src/android/net/ConnectivityMetricsEvent.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016 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; + +/** {@hide} */ +parcelable ConnectivityMetricsEvent; diff --git a/framework/src/android/net/ConnectivityThread.java b/framework/src/android/net/ConnectivityThread.java new file mode 100644 index 0000000000..0b218e738b --- /dev/null +++ b/framework/src/android/net/ConnectivityThread.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 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 android.os.HandlerThread; +import android.os.Looper; + +/** + * Shared singleton connectivity thread for the system. This is a thread for + * connectivity operations such as AsyncChannel connections to system services. + * Various connectivity manager objects can use this singleton as a common + * resource for their handlers instead of creating separate threads of their own. + * @hide + */ +public final class ConnectivityThread extends HandlerThread { + + // A class implementing the lazy holder idiom: the unique static instance + // of ConnectivityThread is instantiated in a thread-safe way (guaranteed by + // the language specs) the first time that Singleton is referenced in get() + // or getInstanceLooper(). + private static class Singleton { + private static final ConnectivityThread INSTANCE = createInstance(); + } + + private ConnectivityThread() { + super("ConnectivityThread"); + } + + private static ConnectivityThread createInstance() { + ConnectivityThread t = new ConnectivityThread(); + t.start(); + return t; + } + + public static ConnectivityThread get() { + return Singleton.INSTANCE; + } + + public static Looper getInstanceLooper() { + return Singleton.INSTANCE.getLooper(); + } +} diff --git a/framework/src/android/net/DhcpInfo.aidl b/framework/src/android/net/DhcpInfo.aidl new file mode 100644 index 0000000000..29cd21fe76 --- /dev/null +++ b/framework/src/android/net/DhcpInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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; + +parcelable DhcpInfo; diff --git a/framework/src/android/net/DhcpInfo.java b/framework/src/android/net/DhcpInfo.java new file mode 100644 index 0000000000..912df67a0b --- /dev/null +++ b/framework/src/android/net/DhcpInfo.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008 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 android.os.Parcel; +import android.os.Parcelable; + +/** + * A simple object for retrieving the results of a DHCP request. + */ +public class DhcpInfo implements Parcelable { + public int ipAddress; + public int gateway; + public int netmask; + public int dns1; + public int dns2; + public int serverAddress; + + public int leaseDuration; + + public DhcpInfo() { + super(); + } + + /** copy constructor {@hide} */ + public DhcpInfo(DhcpInfo source) { + if (source != null) { + ipAddress = source.ipAddress; + gateway = source.gateway; + netmask = source.netmask; + dns1 = source.dns1; + dns2 = source.dns2; + serverAddress = source.serverAddress; + leaseDuration = source.leaseDuration; + } + } + + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("ipaddr "); putAddress(str, ipAddress); + str.append(" gateway "); putAddress(str, gateway); + str.append(" netmask "); putAddress(str, netmask); + str.append(" dns1 "); putAddress(str, dns1); + str.append(" dns2 "); putAddress(str, dns2); + str.append(" DHCP server "); putAddress(str, serverAddress); + str.append(" lease ").append(leaseDuration).append(" seconds"); + + return str.toString(); + } + + private static void putAddress(StringBuffer buf, int addr) { + buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress()); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(ipAddress); + dest.writeInt(gateway); + dest.writeInt(netmask); + dest.writeInt(dns1); + dest.writeInt(dns2); + dest.writeInt(serverAddress); + dest.writeInt(leaseDuration); + } + + /** Implement the Parcelable interface */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public DhcpInfo createFromParcel(Parcel in) { + DhcpInfo info = new DhcpInfo(); + info.ipAddress = in.readInt(); + info.gateway = in.readInt(); + info.netmask = in.readInt(); + info.dns1 = in.readInt(); + info.dns2 = in.readInt(); + info.serverAddress = in.readInt(); + info.leaseDuration = in.readInt(); + return info; + } + + public DhcpInfo[] newArray(int size) { + return new DhcpInfo[size]; + } + }; +} diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java new file mode 100644 index 0000000000..3f7660f570 --- /dev/null +++ b/framework/src/android/net/DnsResolver.java @@ -0,0 +1,577 @@ +/* + * 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 android.net.NetworkUtils.getDnsNetwork; +import static android.net.NetworkUtils.resNetworkCancel; +import static android.net.NetworkUtils.resNetworkQuery; +import static android.net.NetworkUtils.resNetworkResult; +import static android.net.NetworkUtils.resNetworkSend; +import static android.net.util.DnsUtils.haveIpv4; +import static android.net.util.DnsUtils.haveIpv6; +import static android.net.util.DnsUtils.rfc6724Sort; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.system.OsConstants.ENONET; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.CancellationSignal; +import android.os.Looper; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.util.Log; + +import com.android.net.module.util.DnsPacket; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Dns resolver class for asynchronous dns querying + * + * Note that if a client sends a query with more than 1 record in the question section but + * the remote dns server does not support this, it may not respond at all, leading to a timeout. + * + */ +public final class DnsResolver { + private static final String TAG = "DnsResolver"; + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int MAXPACKET = 8 * 1024; + private static final int SLEEP_TIME_MS = 2; + + @IntDef(prefix = { "CLASS_" }, value = { + CLASS_IN + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryClass {} + public static final int CLASS_IN = 1; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_A, + TYPE_AAAA + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryType {} + public static final int TYPE_A = 1; + public static final int TYPE_AAAA = 28; + + @IntDef(prefix = { "FLAG_" }, value = { + FLAG_EMPTY, + FLAG_NO_RETRY, + FLAG_NO_CACHE_STORE, + FLAG_NO_CACHE_LOOKUP + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryFlag {} + public static final int FLAG_EMPTY = 0; + public static final int FLAG_NO_RETRY = 1 << 0; + public static final int FLAG_NO_CACHE_STORE = 1 << 1; + public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2; + + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_PARSE, + ERROR_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + @interface DnsError {} + /** + * Indicates that there was an error parsing the response the query. + * The cause of this error is available via getCause() and is a {@link ParseException}. + */ + public static final int ERROR_PARSE = 0; + /** + * Indicates that there was an error sending the query. + * The cause of this error is available via getCause() and is an ErrnoException. + */ + public static final int ERROR_SYSTEM = 1; + + private static final int NETID_UNSET = 0; + + private static final DnsResolver sInstance = new DnsResolver(); + + /** + * Get instance for DnsResolver + */ + public static @NonNull DnsResolver getInstance() { + return sInstance; + } + + private DnsResolver() {} + + /** + * Base interface for answer callbacks + * + * @param The type of the answer + */ + public interface Callback { + /** + * Success response to + * {@link android.net.DnsResolver#query query()} or + * {@link android.net.DnsResolver#rawQuery rawQuery()}. + * + * Invoked when the answer to a query was successfully parsed. + * + * @param answer answer to the query. + * @param rcode The response code in the DNS response. + * + * {@see android.net.DnsResolver#query query()} + */ + void onAnswer(@NonNull T answer, int rcode); + /** + * Error response to + * {@link android.net.DnsResolver#query query()} or + * {@link android.net.DnsResolver#rawQuery rawQuery()}. + * + * Invoked when there is no valid answer to + * {@link android.net.DnsResolver#query query()} + * {@link android.net.DnsResolver#rawQuery rawQuery()}. + * + * @param error a {@link DnsException} object with additional + * detail regarding the failure + */ + void onError(@NonNull DnsException error); + } + + /** + * Class to represent DNS error + */ + public static class DnsException extends Exception { + /** + * DNS error code as one of the ERROR_* constants + */ + @DnsError public final int code; + + DnsException(@DnsError int code, @Nullable Throwable cause) { + super(cause); + this.code = code; + } + } + + /** + * Send a raw DNS query. + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param query blob message to query + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the caller + * of the result of dns query. + */ + public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final FileDescriptor queryfd; + try { + queryfd = resNetworkSend((network != null) + ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + /** + * Send a DNS query with the specified name, class and query type. + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param nsClass dns class as one of the CLASS_* constants + * @param nsType dns resource record (RR) type as one of the TYPE_* constants + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the caller + * of the result of dns query. + */ + public void rawQuery(@Nullable Network network, @NonNull String domain, + @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final FileDescriptor queryfd; + try { + queryfd = resNetworkQuery((network != null) + ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + private class InetAddressAnswerAccumulator implements Callback { + private final List mAllAnswers; + private final Network mNetwork; + private int mRcode; + private DnsException mDnsException; + private final Callback> mUserCallback; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(@NonNull Network network, int size, + @NonNull Callback> callback) { + mNetwork = network; + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mUserCallback = callback; + } + + private boolean maybeReportError() { + if (mRcode != 0) { + mUserCallback.onAnswer(mAllAnswers, mRcode); + return true; + } + if (mDnsException != null) { + mUserCallback.onError(mDnsException); + return true; + } + return false; + } + + private void maybeReportAnswer() { + if (++mReceivedAnswerCount != mTargetAnswerCount) return; + if (mAllAnswers.isEmpty() && maybeReportError()) return; + mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode); + } + + @Override + public void onAnswer(@NonNull byte[] answer, int rcode) { + // If at least one query succeeded, return an rcode of 0. + // Otherwise, arbitrarily return the first rcode received. + if (mReceivedAnswerCount == 0 || rcode == 0) { + mRcode = rcode; + } + try { + mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses()); + } catch (DnsPacket.ParseException e) { + // Convert the com.android.net.module.util.DnsPacket.ParseException to an + // android.net.ParseException. This is the type that was used in Q and is implied + // by the public documentation of ERROR_PARSE. + // + // DnsPacket cannot throw android.net.ParseException directly because it's @hide. + ParseException pe = new ParseException(e.reason, e.getCause()); + pe.setStackTrace(e.getStackTrace()); + mDnsException = new DnsException(ERROR_PARSE, pe); + } + maybeReportAnswer(); + } + + @Override + public void onError(@NonNull DnsException error) { + mDnsException = error; + maybeReportAnswer(); + } + } + + /** + * Send a DNS query with the specified name on a network with both IPv4 and IPv6, + * get back a set of InetAddresses with rfc6724 sorting style asynchronously. + * + * This method will examine the connection ability on given network, and query IPv4 + * and IPv6 if connection is available. + * + * If at least one query succeeded with valid answer, rcode will be 0 + * + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the + * caller of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback> callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final Network queryNetwork; + try { + queryNetwork = (network != null) ? network : getDnsNetwork(); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + final boolean queryIpv6 = haveIpv6(queryNetwork); + final boolean queryIpv4 = haveIpv4(queryNetwork); + + // This can only happen if queryIpv4 and queryIpv6 are both false. + // This almost certainly means that queryNetwork does not exist or no longer exists. + if (!queryIpv6 && !queryIpv4) { + executor.execute(() -> callback.onError( + new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET)))); + return; + } + + final FileDescriptor v4fd; + final FileDescriptor v6fd; + + int queryCount = 0; + + if (queryIpv6) { + try { + v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, + TYPE_AAAA, flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + queryCount++; + } else v6fd = null; + + // Avoiding gateways drop packets if queries are sent too close together + try { + Thread.sleep(SLEEP_TIME_MS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + + if (queryIpv4) { + try { + v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A, + flags); + } catch (ErrnoException e) { + if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid. + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + queryCount++; + } else v4fd = null; + + final InetAddressAnswerAccumulator accumulator = + new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback); + + synchronized (lock) { + if (queryIpv6) { + registerFDListener(executor, v6fd, accumulator, cancellationSignal, lock); + } + if (queryIpv4) { + registerFDListener(executor, v4fd, accumulator, cancellationSignal, lock); + } + if (cancellationSignal == null) return; + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + if (queryIpv4) cancelQuery(v4fd); + if (queryIpv6) cancelQuery(v6fd); + } + }); + } + } + + /** + * Send a DNS query with the specified name and query type, get back a set of + * InetAddresses with rfc6724 sorting style asynchronously. + * + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param nsType dns resource record (RR) type as one of the TYPE_* constants + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, + @QueryType int nsType, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback> callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final FileDescriptor queryfd; + final Network queryNetwork; + try { + queryNetwork = (network != null) ? network : getDnsNetwork(); + queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType, + flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + final InetAddressAnswerAccumulator accumulator = + new InetAddressAnswerAccumulator(queryNetwork, 1, callback); + synchronized (lock) { + registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + /** + * Class to retrieve DNS response + * + * @hide + */ + public static final class DnsResponse { + public final @NonNull byte[] answerbuf; + public final int rcode; + public DnsResponse(@NonNull byte[] answerbuf, int rcode) { + this.answerbuf = answerbuf; + this.rcode = rcode; + } + } + + private void registerFDListener(@NonNull Executor executor, + @NonNull FileDescriptor queryfd, @NonNull Callback answerCallback, + @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) { + final MessageQueue mainThreadMessageQueue = Looper.getMainLooper().getQueue(); + mainThreadMessageQueue.addOnFileDescriptorEventListener( + queryfd, + FD_EVENTS, + (fd, events) -> { + // b/134310704 + // Unregister fd event listener before resNetworkResult is called to prevent + // race condition caused by fd reused. + // For example when querying v4 and v6, it's possible that the first query ends + // and the fd is closed before the second request starts, which might return + // the same fd for the second request. By that time, the looper must have + // unregistered the fd, otherwise another event listener can't be registered. + mainThreadMessageQueue.removeOnFileDescriptorEventListener(fd); + + executor.execute(() -> { + DnsResponse resp = null; + ErrnoException exception = null; + synchronized (lock) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + try { + resp = resNetworkResult(fd); // Closes fd, marks it invalid. + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + exception = e; + } + } + if (exception != null) { + answerCallback.onError(new DnsException(ERROR_SYSTEM, exception)); + return; + } + answerCallback.onAnswer(resp.answerbuf, resp.rcode); + }); + + // The file descriptor has already been unregistered, so it does not really + // matter what is returned here. In spirit 0 (meaning "unregister this FD") + // is still the closest to what the looper needs to do. When returning 0, + // Looper knows to ignore the fd if it has already been unregistered. + return 0; + }); + } + + private void cancelQuery(@NonNull FileDescriptor queryfd) { + if (!queryfd.valid()) return; + Looper.getMainLooper().getQueue().removeOnFileDescriptorEventListener(queryfd); + resNetworkCancel(queryfd); // Closes fd, marks it invalid. + } + + private void addCancellationSignal(@NonNull CancellationSignal cancellationSignal, + @NonNull FileDescriptor queryfd, @NonNull Object lock) { + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + cancelQuery(queryfd); + } + }); + } + + private static class DnsAddressAnswer extends DnsPacket { + private static final String TAG = "DnsResolver.DnsAddressAnswer"; + private static final boolean DBG = false; + + private final int mQueryType; + + DnsAddressAnswer(@NonNull byte[] data) throws ParseException { + super(data); + if ((mHeader.flags & (1 << 15)) == 0) { + throw new ParseException("Not an answer packet"); + } + if (mHeader.getRecordCount(QDSECTION) == 0) { + throw new ParseException("No question found"); + } + // Expect only one question in question section. + mQueryType = mRecords[QDSECTION].get(0).nsType; + } + + public @NonNull List getAddresses() { + final List results = new ArrayList(); + if (mHeader.getRecordCount(ANSECTION) == 0) return results; + + for (final DnsRecord ansSec : mRecords[ANSECTION]) { + // Only support A and AAAA, also ignore answers if query type != answer type. + int nsType = ansSec.nsType; + if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) { + continue; + } + try { + results.add(InetAddress.getByAddress(ansSec.getRR())); + } catch (UnknownHostException e) { + if (DBG) { + Log.w(TAG, "rr to address fail"); + } + } + } + return results; + } + } + +} diff --git a/framework/src/android/net/ICaptivePortal.aidl b/framework/src/android/net/ICaptivePortal.aidl new file mode 100644 index 0000000000..fe21905c70 --- /dev/null +++ b/framework/src/android/net/ICaptivePortal.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2015, 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; + +/** + * Interface to inform NetworkMonitor of decisions of app handling captive portal. + * @hide + */ +oneway interface ICaptivePortal { + void appRequest(int request); + void appResponse(int response); + void logEvent(int eventId, String packageName); +} diff --git a/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl b/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl new file mode 100644 index 0000000000..82b64a9280 --- /dev/null +++ b/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl @@ -0,0 +1,28 @@ +/** + * + * 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 android.net.ConnectivityDiagnosticsManager; +import android.net.Network; + +/** @hide */ +oneway interface IConnectivityDiagnosticsCallback { + void onConnectivityReportAvailable(in ConnectivityDiagnosticsManager.ConnectivityReport report); + void onDataStallSuspected(in ConnectivityDiagnosticsManager.DataStallReport report); + void onNetworkConnectivityReported(in Network n, boolean hasConnectivity); +} \ No newline at end of file diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl new file mode 100644 index 0000000000..1b4d2e4139 --- /dev/null +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2008, 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 android.app.PendingIntent; +import android.net.ConnectionInfo; +import android.net.ConnectivityDiagnosticsManager; +import android.net.IConnectivityDiagnosticsCallback; +import android.net.IQosCallback; +import android.net.ISocketKeepaliveCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkRequest; +import android.net.NetworkState; +import android.net.ProxyInfo; +import android.net.UidRange; +import android.net.QosSocketInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.INetworkActivityListener; +import android.os.Messenger; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.ResultReceiver; + +import com.android.connectivity.aidl.INetworkAgent; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; + +/** + * Interface that answers queries about, and allows changing, the + * state of network connectivity. + */ +/** {@hide} */ +interface IConnectivityManager +{ + Network getActiveNetwork(); + Network getActiveNetworkForUid(int uid, boolean ignoreBlocked); + @UnsupportedAppUsage + NetworkInfo getActiveNetworkInfo(); + NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked); + @UnsupportedAppUsage(maxTargetSdk = 28) + NetworkInfo getNetworkInfo(int networkType); + NetworkInfo getNetworkInfoForUid(in Network network, int uid, boolean ignoreBlocked); + @UnsupportedAppUsage + NetworkInfo[] getAllNetworkInfo(); + Network getNetworkForType(int networkType); + Network[] getAllNetworks(); + NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser( + int userId, String callingPackageName); + + boolean isNetworkSupported(int networkType); + + @UnsupportedAppUsage + LinkProperties getActiveLinkProperties(); + LinkProperties getLinkPropertiesForType(int networkType); + LinkProperties getLinkProperties(in Network network); + + NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName); + + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + NetworkState[] getAllNetworkState(); + + boolean isActiveNetworkMetered(); + + boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, + String callingPackageName, String callingAttributionTag); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getLastTetherError} as alternative") + int getLastTetherError(String iface); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetherableIfaces} as alternative") + String[] getTetherableIfaces(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetheredIfaces} as alternative") + String[] getTetheredIfaces(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetheringErroredIfaces} " + + "as Alternative") + String[] getTetheringErroredIfaces(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetherableUsbRegexs} as alternative") + String[] getTetherableUsbRegexs(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetherableWifiRegexs} as alternative") + String[] getTetherableWifiRegexs(); + + @UnsupportedAppUsage(maxTargetSdk = 28) + void reportInetCondition(int networkType, int percentage); + + void reportNetworkConnectivity(in Network network, boolean hasConnectivity); + + ProxyInfo getGlobalProxy(); + + void setGlobalProxy(in ProxyInfo p); + + ProxyInfo getProxyForNetwork(in Network nework); + + boolean prepareVpn(String oldPackage, String newPackage, int userId); + + void setVpnPackageAuthorization(String packageName, int userId, int vpnType); + + ParcelFileDescriptor establishVpn(in VpnConfig config); + + boolean provisionVpnProfile(in VpnProfile profile, String packageName); + + void deleteVpnProfile(String packageName); + + void startVpnProfile(String packageName); + + void stopVpnProfile(String packageName); + + VpnConfig getVpnConfig(int userId); + + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void startLegacyVpn(in VpnProfile profile); + + LegacyVpnInfo getLegacyVpnInfo(int userId); + + boolean updateLockdownVpn(); + boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); + boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, + in List lockdownWhitelist); + String getAlwaysOnVpnPackage(int userId); + boolean isVpnLockdownEnabled(int userId); + List getVpnLockdownWhitelist(int userId); + void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); + + void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); + + void setAirplaneMode(boolean enable); + + boolean requestBandwidthUpdate(in Network network); + + int registerNetworkFactory(in Messenger messenger, in String name); + void unregisterNetworkFactory(in Messenger messenger); + + int registerNetworkProvider(in Messenger messenger, in String name); + void unregisterNetworkProvider(in Messenger messenger); + + void declareNetworkRequestUnfulfillable(in NetworkRequest request); + + Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, + in NetworkCapabilities nc, int score, in NetworkAgentConfig config, + in int factorySerialNumber); + + NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, + in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, + String callingPackageName, String callingAttributionTag); + + NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, + in PendingIntent operation, String callingPackageName, String callingAttributionTag); + + void releasePendingNetworkRequest(in PendingIntent operation); + + NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities, + in Messenger messenger, in IBinder binder, String callingPackageName); + + void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, + in PendingIntent operation, String callingPackageName); + + void releaseNetworkRequest(in NetworkRequest networkRequest); + + void setAcceptUnvalidated(in Network network, boolean accept, boolean always); + void setAcceptPartialConnectivity(in Network network, boolean accept, boolean always); + void setAvoidUnvalidated(in Network network); + void startCaptivePortalApp(in Network network); + void startCaptivePortalAppInternal(in Network network, in Bundle appExtras); + + boolean shouldAvoidBadWifi(); + int getMultipathPreference(in Network Network); + + NetworkRequest getDefaultRequest(); + + int getRestoreDefaultNetworkDelay(int networkType); + + boolean addVpnAddress(String address, int prefixLength); + boolean removeVpnAddress(String address, int prefixLength); + boolean setUnderlyingNetworksForVpn(in Network[] networks); + + void factoryReset(); + + void startNattKeepalive(in Network network, int intervalSeconds, + in ISocketKeepaliveCallback cb, String srcAddr, int srcPort, String dstAddr); + + void startNattKeepaliveWithFd(in Network network, in ParcelFileDescriptor pfd, int resourceId, + int intervalSeconds, in ISocketKeepaliveCallback cb, String srcAddr, + String dstAddr); + + void startTcpKeepalive(in Network network, in ParcelFileDescriptor pfd, int intervalSeconds, + in ISocketKeepaliveCallback cb); + + void stopKeepalive(in Network network, int slot); + + String getCaptivePortalServerUrl(); + + byte[] getNetworkWatchlistConfigHash(); + + int getConnectionOwnerUid(in ConnectionInfo connectionInfo); + boolean isCallerCurrentAlwaysOnVpnApp(); + boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + + void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, + in NetworkRequest request, String callingPackageName); + void unregisterConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback); + + IBinder startOrGetTestNetworkService(); + + void simulateDataStall(int detectionMethod, long timestampMillis, in Network network, + in PersistableBundle extras); + + void systemReady(); + + void registerNetworkActivityListener(in INetworkActivityListener l); + + void unregisterNetworkActivityListener(in INetworkActivityListener l); + + boolean isDefaultNetworkActive(); + + void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); + void unregisterQosCallback(in IQosCallback callback); +} diff --git a/framework/src/android/net/ISocketKeepaliveCallback.aidl b/framework/src/android/net/ISocketKeepaliveCallback.aidl new file mode 100644 index 0000000000..020fbcacbf --- /dev/null +++ b/framework/src/android/net/ISocketKeepaliveCallback.aidl @@ -0,0 +1,34 @@ +/** + * 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; + +/** + * Callback to provide status changes of keepalive offload. + * + * @hide + */ +oneway interface ISocketKeepaliveCallback +{ + /** The keepalive was successfully started. */ + void onStarted(int slot); + /** The keepalive was successfully stopped. */ + void onStopped(); + /** The keepalive was stopped because of an error. */ + void onError(int error); + /** The keepalive on a TCP socket was stopped because the socket received data. */ + void onDataReceived(); +} diff --git a/framework/src/android/net/ITestNetworkManager.aidl b/framework/src/android/net/ITestNetworkManager.aidl new file mode 100644 index 0000000000..2a863adde5 --- /dev/null +++ b/framework/src/android/net/ITestNetworkManager.aidl @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2018, 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 android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.TestNetworkInterface; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + +/** + * Interface that allows for creation and management of test-only networks. + * + * @hide + */ +interface ITestNetworkManager +{ + TestNetworkInterface createTunInterface(in LinkAddress[] linkAddrs); + TestNetworkInterface createTapInterface(); + + void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered, + in int[] administratorUids, in IBinder binder); + + void teardownTestNetwork(int netId); +} diff --git a/framework/src/android/net/InetAddresses.java b/framework/src/android/net/InetAddresses.java new file mode 100644 index 0000000000..01b795e456 --- /dev/null +++ b/framework/src/android/net/InetAddresses.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 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 android.annotation.NonNull; + +import libcore.net.InetAddressUtils; + +import java.net.InetAddress; + +/** + * Utility methods for {@link InetAddress} implementations. + */ +public class InetAddresses { + + private InetAddresses() {} + + /** + * Checks to see if the {@code address} is a numeric address (such as {@code "192.0.2.1"} or + * {@code "2001:db8::1:2"}). + * + *

A numeric address is either an IPv4 address containing exactly 4 decimal numbers or an + * IPv6 numeric address. IPv4 addresses that consist of either hexadecimal or octal digits or + * do not have exactly 4 numbers are not treated as numeric. + * + *

This method will never do a DNS lookup. + * + * @param address the address to parse. + * @return true if the supplied address is numeric, false otherwise. + */ + public static boolean isNumericAddress(@NonNull String address) { + return InetAddressUtils.isNumericAddress(address); + } + + /** + * Returns an InetAddress corresponding to the given numeric address (such + * as {@code "192.168.0.1"} or {@code "2001:4860:800d::68"}). + * + *

See {@link #isNumericAddress(String)} (String)} for a definition as to what constitutes a + * numeric address. + * + *

This method will never do a DNS lookup. + * + * @param address the address to parse, must be numeric. + * @return an {@link InetAddress} instance corresponding to the address. + * @throws IllegalArgumentException if {@code address} is not a numeric address. + */ + public static @NonNull InetAddress parseNumericAddress(@NonNull String address) { + return InetAddressUtils.parseNumericAddress(address); + } +} diff --git a/framework/src/android/net/InterfaceConfiguration.aidl b/framework/src/android/net/InterfaceConfiguration.aidl new file mode 100644 index 0000000000..8aa5e34528 --- /dev/null +++ b/framework/src/android/net/InterfaceConfiguration.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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; + +parcelable InterfaceConfiguration; diff --git a/framework/src/android/net/InvalidPacketException.java b/framework/src/android/net/InvalidPacketException.java new file mode 100644 index 0000000000..1873d778c0 --- /dev/null +++ b/framework/src/android/net/InvalidPacketException.java @@ -0,0 +1,66 @@ +/* + * 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 android.annotation.IntDef; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Thrown when a packet is invalid. + * @hide + */ +@SystemApi +public final class InvalidPacketException extends Exception { + private final int mError; + + // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS. + /** Invalid IP address. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + + // Must match SocketKeepalive#ERROR_INVALID_PORT. + /** Invalid port number. */ + public static final int ERROR_INVALID_PORT = -22; + + // Must match SocketKeepalive#ERROR_INVALID_LENGTH. + /** Invalid packet length. */ + public static final int ERROR_INVALID_LENGTH = -23; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_INVALID_IP_ADDRESS, + ERROR_INVALID_PORT, + ERROR_INVALID_LENGTH + }) + public @interface ErrorCode {} + + /** + * This packet is invalid. + * See the error code for details. + */ + public InvalidPacketException(@ErrorCode final int error) { + this.mError = error; + } + + /** Get error code. */ + public int getError() { + return mError; + } +} diff --git a/framework/src/android/net/IpConfiguration.aidl b/framework/src/android/net/IpConfiguration.aidl new file mode 100644 index 0000000000..7a30f0e79c --- /dev/null +++ b/framework/src/android/net/IpConfiguration.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 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; + +parcelable IpConfiguration; diff --git a/framework/src/android/net/IpConfiguration.java b/framework/src/android/net/IpConfiguration.java new file mode 100644 index 0000000000..d5f8b2edb6 --- /dev/null +++ b/framework/src/android/net/IpConfiguration.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2014 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A class representing a configured network. + * @hide + */ +@SystemApi +public final class IpConfiguration implements Parcelable { + private static final String TAG = "IpConfiguration"; + + // This enum has been used by apps through reflection for many releases. + // Therefore they can't just be removed. Duplicating these constants to + // give an alternate SystemApi is a worse option than exposing them. + @SuppressLint("Enum") + public enum IpAssignment { + /* Use statically configured IP settings. Configuration can be accessed + * with staticIpConfiguration */ + STATIC, + /* Use dynamically configured IP settings */ + DHCP, + /* no IP details are assigned, this is used to indicate + * that any existing IP settings should be retained */ + UNASSIGNED + } + + /** @hide */ + public IpAssignment ipAssignment; + + /** @hide */ + public StaticIpConfiguration staticIpConfiguration; + + // This enum has been used by apps through reflection for many releases. + // Therefore they can't just be removed. Duplicating these constants to + // give an alternate SystemApi is a worse option than exposing them. + @SuppressLint("Enum") + public enum ProxySettings { + /* No proxy is to be used. Any existing proxy settings + * should be cleared. */ + NONE, + /* Use statically configured proxy. Configuration can be accessed + * with httpProxy. */ + STATIC, + /* no proxy details are assigned, this is used to indicate + * that any existing proxy settings should be retained */ + UNASSIGNED, + /* Use a Pac based proxy. + */ + PAC + } + + /** @hide */ + public ProxySettings proxySettings; + + /** @hide */ + @UnsupportedAppUsage + public ProxyInfo httpProxy; + + private void init(IpAssignment ipAssignment, + ProxySettings proxySettings, + StaticIpConfiguration staticIpConfiguration, + ProxyInfo httpProxy) { + this.ipAssignment = ipAssignment; + this.proxySettings = proxySettings; + this.staticIpConfiguration = (staticIpConfiguration == null) ? + null : new StaticIpConfiguration(staticIpConfiguration); + this.httpProxy = (httpProxy == null) ? + null : new ProxyInfo(httpProxy); + } + + public IpConfiguration() { + init(IpAssignment.UNASSIGNED, ProxySettings.UNASSIGNED, null, null); + } + + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public IpConfiguration(IpAssignment ipAssignment, + ProxySettings proxySettings, + StaticIpConfiguration staticIpConfiguration, + ProxyInfo httpProxy) { + init(ipAssignment, proxySettings, staticIpConfiguration, httpProxy); + } + + public IpConfiguration(@NonNull IpConfiguration source) { + this(); + if (source != null) { + init(source.ipAssignment, source.proxySettings, + source.staticIpConfiguration, source.httpProxy); + } + } + + public @NonNull IpAssignment getIpAssignment() { + return ipAssignment; + } + + public void setIpAssignment(@NonNull IpAssignment ipAssignment) { + this.ipAssignment = ipAssignment; + } + + public @Nullable StaticIpConfiguration getStaticIpConfiguration() { + return staticIpConfiguration; + } + + public void setStaticIpConfiguration(@Nullable StaticIpConfiguration staticIpConfiguration) { + this.staticIpConfiguration = staticIpConfiguration; + } + + public @NonNull ProxySettings getProxySettings() { + return proxySettings; + } + + public void setProxySettings(@NonNull ProxySettings proxySettings) { + this.proxySettings = proxySettings; + } + + public @Nullable ProxyInfo getHttpProxy() { + return httpProxy; + } + + public void setHttpProxy(@Nullable ProxyInfo httpProxy) { + this.httpProxy = httpProxy; + } + + @Override + public String toString() { + StringBuilder sbuf = new StringBuilder(); + sbuf.append("IP assignment: " + ipAssignment.toString()); + sbuf.append("\n"); + if (staticIpConfiguration != null) { + sbuf.append("Static configuration: " + staticIpConfiguration.toString()); + sbuf.append("\n"); + } + sbuf.append("Proxy settings: " + proxySettings.toString()); + sbuf.append("\n"); + if (httpProxy != null) { + sbuf.append("HTTP proxy: " + httpProxy.toString()); + sbuf.append("\n"); + } + + return sbuf.toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof IpConfiguration)) { + return false; + } + + IpConfiguration other = (IpConfiguration) o; + return this.ipAssignment == other.ipAssignment && + this.proxySettings == other.proxySettings && + Objects.equals(this.staticIpConfiguration, other.staticIpConfiguration) && + Objects.equals(this.httpProxy, other.httpProxy); + } + + @Override + public int hashCode() { + return 13 + (staticIpConfiguration != null ? staticIpConfiguration.hashCode() : 0) + + 17 * ipAssignment.ordinal() + + 47 * proxySettings.ordinal() + + 83 * httpProxy.hashCode(); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(ipAssignment.name()); + dest.writeString(proxySettings.name()); + dest.writeParcelable(staticIpConfiguration, flags); + dest.writeParcelable(httpProxy, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + public IpConfiguration createFromParcel(Parcel in) { + IpConfiguration config = new IpConfiguration(); + config.ipAssignment = IpAssignment.valueOf(in.readString()); + config.proxySettings = ProxySettings.valueOf(in.readString()); + config.staticIpConfiguration = in.readParcelable(null); + config.httpProxy = in.readParcelable(null); + return config; + } + + public IpConfiguration[] newArray(int size) { + return new IpConfiguration[size]; + } + }; +} diff --git a/framework/src/android/net/IpPrefix.aidl b/framework/src/android/net/IpPrefix.aidl new file mode 100644 index 0000000000..0d70f2a1ed --- /dev/null +++ b/framework/src/android/net/IpPrefix.aidl @@ -0,0 +1,22 @@ +/** + * + * Copyright (C) 2014 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; + +// @JavaOnlyStableParcelable only affects the parcelable when built as stable aidl (aidl_interface +// build rule). IpPrefix is also used in cpp but only as non-stable aidl. +@JavaOnlyStableParcelable parcelable IpPrefix cpp_header "binder/IpPrefix.h"; diff --git a/framework/src/android/net/IpPrefix.java b/framework/src/android/net/IpPrefix.java new file mode 100644 index 0000000000..bcb65fab85 --- /dev/null +++ b/framework/src/android/net/IpPrefix.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2014 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 android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Pair; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Comparator; + +/** + * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a + * power of two boundary (also known as an "IP subnet"). A prefix is specified by two pieces of + * information: + * + *

    + *
  • A starting IP address (IPv4 or IPv6). This is the first IP address of the prefix. + *
  • A prefix length. This specifies the length of the prefix by specifing the number of bits + * in the IP address, starting from the most significant bit in network byte order, that + * are constant for all addresses in the prefix. + *
+ * + * For example, the prefix 192.0.2.0/24 covers the 256 IPv4 addresses from + * 192.0.2.0 to 192.0.2.255, inclusive, and the prefix + * 2001:db8:1:2 covers the 2^64 IPv6 addresses from 2001:db8:1:2:: to + * 2001:db8:1:2:ffff:ffff:ffff:ffff, inclusive. + * + * Objects of this class are immutable. + */ +public final class IpPrefix implements Parcelable { + private final byte[] address; // network byte order + private final int prefixLength; + + private void checkAndMaskAddressAndPrefixLength() { + if (address.length != 4 && address.length != 16) { + throw new IllegalArgumentException( + "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); + } + NetworkUtils.maskRawAddress(address, prefixLength); + } + + /** + * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in + * network byte order and a prefix length. Silently truncates the address to the prefix length, + * so for example {@code 192.0.2.1/24} is silently converted to {@code 192.0.2.0/24}. + * + * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long. + * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * + * @hide + */ + public IpPrefix(@NonNull byte[] address, @IntRange(from = 0, to = 128) int prefixLength) { + this.address = address.clone(); + this.prefixLength = prefixLength; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Constructs a new {@code IpPrefix} from an IPv4 or IPv6 address and a prefix length. Silently + * truncates the address to the prefix length, so for example {@code 192.0.2.1/24} is silently + * converted to {@code 192.0.2.0/24}. + * + * @param address the IP address. Must be non-null. + * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @hide + */ + @SystemApi + public IpPrefix(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength) { + // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, + // which is unnecessary because getAddress() already returns a clone. + this.address = address.getAddress(); + this.prefixLength = prefixLength; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Constructs a new IpPrefix from a string such as "192.0.2.1/24" or "2001:db8::1/64". + * Silently truncates the address to the prefix length, so for example {@code 192.0.2.1/24} + * is silently converted to {@code 192.0.2.0/24}. + * + * @param prefix the prefix to parse + * + * @hide + */ + @SystemApi + public IpPrefix(@NonNull String prefix) { + // We don't reuse the (InetAddress, int) constructor because "error: call to this must be + // first statement in constructor". We could factor out setting the member variables to an + // init() method, but if we did, then we'd have to make the members non-final, or "error: + // cannot assign a value to final variable address". So we just duplicate the code here. + Pair ipAndMask = NetworkUtils.parseIpAndMask(prefix); + this.address = ipAndMask.first.getAddress(); + this.prefixLength = ipAndMask.second; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Compares this {@code IpPrefix} object against the specified object in {@code obj}. Two + * objects are equal if they have the same startAddress and prefixLength. + * + * @param obj the object to be tested for equality. + * @return {@code true} if both objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof IpPrefix)) { + return false; + } + IpPrefix that = (IpPrefix) obj; + return Arrays.equals(this.address, that.address) && this.prefixLength == that.prefixLength; + } + + /** + * Gets the hashcode of the represented IP prefix. + * + * @return the appropriate hashcode value. + */ + @Override + public int hashCode() { + return Arrays.hashCode(address) + 11 * prefixLength; + } + + /** + * Returns a copy of the first IP address in the prefix. Modifying the returned object does not + * change this object's contents. + * + * @return the address in the form of a byte array. + */ + public @NonNull InetAddress getAddress() { + try { + return InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte + // array is the wrong length, but we check that in the constructor. + throw new IllegalArgumentException("Address is invalid"); + } + } + + /** + * Returns a copy of the IP address bytes in network order (the highest order byte is the zeroth + * element). Modifying the returned array does not change this object's contents. + * + * @return the address in the form of a byte array. + */ + public @NonNull byte[] getRawAddress() { + return address.clone(); + } + + /** + * Returns the prefix length of this {@code IpPrefix}. + * + * @return the prefix length. + */ + @IntRange(from = 0, to = 128) + public int getPrefixLength() { + return prefixLength; + } + + /** + * Determines whether the prefix contains the specified address. + * + * @param address An {@link InetAddress} to test. + * @return {@code true} if the prefix covers the given address. {@code false} otherwise. + */ + public boolean contains(@NonNull InetAddress address) { + byte[] addrBytes = address.getAddress(); + if (addrBytes == null || addrBytes.length != this.address.length) { + return false; + } + NetworkUtils.maskRawAddress(addrBytes, prefixLength); + return Arrays.equals(this.address, addrBytes); + } + + /** + * Returns whether the specified prefix is entirely contained in this prefix. + * + * Note this is mathematical inclusion, so a prefix is always contained within itself. + * @param otherPrefix the prefix to test + * @hide + */ + public boolean containsPrefix(@NonNull IpPrefix otherPrefix) { + if (otherPrefix.getPrefixLength() < prefixLength) return false; + final byte[] otherAddress = otherPrefix.getRawAddress(); + NetworkUtils.maskRawAddress(otherAddress, prefixLength); + return Arrays.equals(otherAddress, address); + } + + /** + * @hide + */ + public boolean isIPv6() { + return getAddress() instanceof Inet6Address; + } + + /** + * @hide + */ + public boolean isIPv4() { + return getAddress() instanceof Inet4Address; + } + + /** + * Returns a string representation of this {@code IpPrefix}. + * + * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::/64"}. + */ + public String toString() { + try { + return InetAddress.getByAddress(address).getHostAddress() + "/" + prefixLength; + } catch(UnknownHostException e) { + // Cosmic rays? + throw new IllegalStateException("IpPrefix with invalid address! Shouldn't happen.", e); + } + } + + /** + * Implement the Parcelable interface. + */ + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(address); + dest.writeInt(prefixLength); + } + + /** + * Returns a comparator ordering IpPrefixes by length, shorter to longer. + * Contents of the address will break ties. + * @hide + */ + public static Comparator lengthComparator() { + return new Comparator() { + @Override + public int compare(IpPrefix prefix1, IpPrefix prefix2) { + if (prefix1.isIPv4()) { + if (prefix2.isIPv6()) return -1; + } else { + if (prefix2.isIPv4()) return 1; + } + final int p1len = prefix1.getPrefixLength(); + final int p2len = prefix2.getPrefixLength(); + if (p1len < p2len) return -1; + if (p2len < p1len) return 1; + final byte[] a1 = prefix1.address; + final byte[] a2 = prefix2.address; + final int len = a1.length < a2.length ? a1.length : a2.length; + for (int i = 0; i < len; ++i) { + if (a1[i] < a2[i]) return -1; + if (a1[i] > a2[i]) return 1; + } + if (a2.length < len) return 1; + if (a1.length < len) return -1; + return 0; + } + }; + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public IpPrefix createFromParcel(Parcel in) { + byte[] address = in.createByteArray(); + int prefixLength = in.readInt(); + return new IpPrefix(address, prefixLength); + } + + public IpPrefix[] newArray(int size) { + return new IpPrefix[size]; + } + }; +} diff --git a/framework/src/android/net/KeepalivePacketData.aidl b/framework/src/android/net/KeepalivePacketData.aidl new file mode 100644 index 0000000000..d456b53fd1 --- /dev/null +++ b/framework/src/android/net/KeepalivePacketData.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 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; + +parcelable KeepalivePacketData; diff --git a/framework/src/android/net/KeepalivePacketData.java b/framework/src/android/net/KeepalivePacketData.java new file mode 100644 index 0000000000..5877f1f4e2 --- /dev/null +++ b/framework/src/android/net/KeepalivePacketData.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 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 android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS; +import static android.net.InvalidPacketException.ERROR_INVALID_PORT; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.util.Log; + +import com.android.net.module.util.IpUtils; + +import java.net.InetAddress; + +/** + * Represents the actual packets that are sent by the + * {@link android.net.SocketKeepalive} API. + * @hide + */ +@SystemApi +public class KeepalivePacketData { + private static final String TAG = "KeepalivePacketData"; + + /** Source IP address */ + @NonNull + private final InetAddress mSrcAddress; + + /** Destination IP address */ + @NonNull + private final InetAddress mDstAddress; + + /** Source port */ + private final int mSrcPort; + + /** Destination port */ + private final int mDstPort; + + /** Packet data. A raw byte string of packet data, not including the link-layer header. */ + private final byte[] mPacket; + + // Note: If you add new fields, please modify the parcelling code in the child classes. + + + // This should only be constructed via static factory methods, such as + // nattKeepalivePacket. + /** + * A holding class for data necessary to build a keepalive packet. + */ + protected KeepalivePacketData(@NonNull InetAddress srcAddress, + @IntRange(from = 0, to = 65535) int srcPort, @NonNull InetAddress dstAddress, + @IntRange(from = 0, to = 65535) int dstPort, + @NonNull byte[] data) throws InvalidPacketException { + this.mSrcAddress = srcAddress; + this.mDstAddress = dstAddress; + this.mSrcPort = srcPort; + this.mDstPort = dstPort; + this.mPacket = data; + + // Check we have two IP addresses of the same family. + if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName() + .equals(dstAddress.getClass().getName())) { + Log.e(TAG, "Invalid or mismatched InetAddresses in KeepalivePacketData"); + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + // Check the ports. + if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) { + Log.e(TAG, "Invalid ports in KeepalivePacketData"); + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + } + + /** Get source IP address. */ + @NonNull + public InetAddress getSrcAddress() { + return mSrcAddress; + } + + /** Get destination IP address. */ + @NonNull + public InetAddress getDstAddress() { + return mDstAddress; + } + + /** Get source port number. */ + public int getSrcPort() { + return mSrcPort; + } + + /** Get destination port number. */ + public int getDstPort() { + return mDstPort; + } + + /** + * Returns a byte array of the given packet data. + */ + @NonNull + public byte[] getPacket() { + return mPacket.clone(); + } + +} diff --git a/framework/src/android/net/LinkAddress.aidl b/framework/src/android/net/LinkAddress.aidl new file mode 100644 index 0000000000..9c804db08d --- /dev/null +++ b/framework/src/android/net/LinkAddress.aidl @@ -0,0 +1,21 @@ +/** + * + * Copyright (C) 2010 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; + +@JavaOnlyStableParcelable parcelable LinkAddress; + diff --git a/framework/src/android/net/LinkAddress.java b/framework/src/android/net/LinkAddress.java new file mode 100644 index 0000000000..d1bdaa078c --- /dev/null +++ b/framework/src/android/net/LinkAddress.java @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2010 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 android.system.OsConstants.IFA_F_DADFAILED; +import static android.system.OsConstants.IFA_F_DEPRECATED; +import static android.system.OsConstants.IFA_F_OPTIMISTIC; +import static android.system.OsConstants.IFA_F_PERMANENT; +import static android.system.OsConstants.IFA_F_TENTATIVE; +import static android.system.OsConstants.RT_SCOPE_HOST; +import static android.system.OsConstants.RT_SCOPE_LINK; +import static android.system.OsConstants.RT_SCOPE_SITE; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.Pair; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * Identifies an IP address on a network link. + * + * A {@code LinkAddress} consists of: + *
    + *
  • An IP address and prefix length (e.g., {@code 2001:db8::1/64} or {@code 192.0.2.1/24}). + * The address must be unicast, as multicast addresses cannot be assigned to interfaces. + *
  • Address flags: A bitmask of {@code OsConstants.IFA_F_*} values representing properties + * of the address (e.g., {@code android.system.OsConstants.IFA_F_OPTIMISTIC}). + *
  • Address scope: One of the {@code OsConstants.IFA_F_*} values; defines the scope in which + * the address is unique (e.g., + * {@code android.system.OsConstants.RT_SCOPE_LINK} or + * {@code android.system.OsConstants.RT_SCOPE_UNIVERSE}). + *
+ */ +public class LinkAddress implements Parcelable { + + /** + * Indicates the deprecation or expiration time is unknown + * @hide + */ + @SystemApi + public static final long LIFETIME_UNKNOWN = -1; + + /** + * Indicates this address is permanent. + * @hide + */ + @SystemApi + public static final long LIFETIME_PERMANENT = Long.MAX_VALUE; + + /** + * IPv4 or IPv6 address. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private InetAddress address; + + /** + * Prefix length. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private int prefixLength; + + /** + * Address flags. A bitmask of {@code IFA_F_*} values. Note that {@link #getFlags()} may not + * return these exact values. For example, it may set or clear the {@code IFA_F_DEPRECATED} + * flag depending on the current preferred lifetime. + */ + private int flags; + + /** + * Address scope. One of the RT_SCOPE_* constants. + */ + private int scope; + + /** + * The time, as reported by {@link SystemClock#elapsedRealtime}, when this LinkAddress will be + * or was deprecated. At the time existing connections can still use this address until it + * expires, but new connections should use the new address. {@link #LIFETIME_UNKNOWN} indicates + * this information is not available. {@link #LIFETIME_PERMANENT} indicates this + * {@link LinkAddress} will never be deprecated. + */ + private long deprecationTime; + + /** + * The time, as reported by {@link SystemClock#elapsedRealtime}, when this {@link LinkAddress} + * will expire and be removed from the interface. {@link #LIFETIME_UNKNOWN} indicates this + * information is not available. {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} + * will never expire. + */ + private long expirationTime; + + /** + * Utility function to determines the scope of a unicast address. Per RFC 4291 section 2.5 and + * RFC 6724 section 3.2. + * @hide + */ + private static int scopeForUnicastAddress(InetAddress addr) { + if (addr.isAnyLocalAddress()) { + return RT_SCOPE_HOST; + } + + if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) { + return RT_SCOPE_LINK; + } + + // isSiteLocalAddress() returns true for private IPv4 addresses, but RFC 6724 section 3.2 + // says that they are assigned global scope. + if (!(addr instanceof Inet4Address) && addr.isSiteLocalAddress()) { + return RT_SCOPE_SITE; + } + + return RT_SCOPE_UNIVERSE; + } + + /** + * Utility function to check if |address| is a Unique Local IPv6 Unicast Address + * (a.k.a. "ULA"; RFC 4193). + * + * Per RFC 4193 section 8, fc00::/7 identifies these addresses. + */ + private boolean isIpv6ULA() { + if (isIpv6()) { + byte[] bytes = address.getAddress(); + return ((bytes[0] & (byte)0xfe) == (byte)0xfc); + } + return false; + } + + /** + * @return true if the address is IPv6. + * @hide + */ + @SystemApi + public boolean isIpv6() { + return address instanceof Inet6Address; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return true if the address is IPv6. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean isIPv6() { + return isIpv6(); + } + + /** + * @return true if the address is IPv4 or is a mapped IPv4 address. + * @hide + */ + @SystemApi + public boolean isIpv4() { + return address instanceof Inet4Address; + } + + /** + * Utility function for the constructors. + */ + private void init(InetAddress address, int prefixLength, int flags, int scope, + long deprecationTime, long expirationTime) { + if (address == null || + address.isMulticastAddress() || + prefixLength < 0 || + (address instanceof Inet4Address && prefixLength > 32) || + (prefixLength > 128)) { + throw new IllegalArgumentException("Bad LinkAddress params " + address + + "/" + prefixLength); + } + + // deprecation time and expiration time must be both provided, or neither. + if ((deprecationTime == LIFETIME_UNKNOWN) != (expirationTime == LIFETIME_UNKNOWN)) { + throw new IllegalArgumentException( + "Must not specify only one of deprecation time and expiration time"); + } + + // deprecation time needs to be a positive value. + if (deprecationTime != LIFETIME_UNKNOWN && deprecationTime < 0) { + throw new IllegalArgumentException("invalid deprecation time " + deprecationTime); + } + + // expiration time needs to be a positive value. + if (expirationTime != LIFETIME_UNKNOWN && expirationTime < 0) { + throw new IllegalArgumentException("invalid expiration time " + expirationTime); + } + + // expiration time can't be earlier than deprecation time + if (deprecationTime != LIFETIME_UNKNOWN && expirationTime != LIFETIME_UNKNOWN + && expirationTime < deprecationTime) { + throw new IllegalArgumentException("expiration earlier than deprecation (" + + deprecationTime + ", " + expirationTime + ")"); + } + + this.address = address; + this.prefixLength = prefixLength; + this.flags = flags; + this.scope = scope; + this.deprecationTime = deprecationTime; + this.expirationTime = expirationTime; + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InetAddress} and prefix length, with + * the specified flags and scope. Flags and scope are not checked for validity. + * + * @param address The IP address. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address. + * @param scope An integer defining the scope in which the address is unique (e.g., + * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). + * @hide + */ + @SystemApi + public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength, + int flags, int scope) { + init(address, prefixLength, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN); + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InetAddress}, prefix length, with + * the specified flags, scope, deprecation time, and expiration time. Flags and scope are not + * checked for validity. The value of the {@code IFA_F_DEPRECATED} and {@code IFA_F_PERMANENT} + * flag will be adjusted based on the passed-in lifetimes. + * + * @param address The IP address. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address. + * @param scope An integer defining the scope in which the address is unique (e.g., + * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). + * @param deprecationTime The time, as reported by {@link SystemClock#elapsedRealtime}, when + * this {@link LinkAddress} will be or was deprecated. At the time + * existing connections can still use this address until it expires, but + * new connections should use the new address. {@link #LIFETIME_UNKNOWN} + * indicates this information is not available. + * {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} will + * never be deprecated. + * @param expirationTime The time, as reported by {@link SystemClock#elapsedRealtime}, when this + * {@link LinkAddress} will expire and be removed from the interface. + * {@link #LIFETIME_UNKNOWN} indicates this information is not available. + * {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} will + * never expire. + * @hide + */ + @SystemApi + public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength, + int flags, int scope, long deprecationTime, long expirationTime) { + init(address, prefixLength, flags, scope, deprecationTime, expirationTime); + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InetAddress} and a prefix length. + * The flags are set to zero and the scope is determined from the address. + * @param address The IP address. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @hide + */ + @SystemApi + public LinkAddress(@NonNull InetAddress address, + @IntRange(from = 0, to = 128) int prefixLength) { + this(address, prefixLength, 0, 0); + this.scope = scopeForUnicastAddress(address); + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InterfaceAddress}. + * The flags are set to zero and the scope is determined from the address. + * @param interfaceAddress The interface address. + * @hide + */ + public LinkAddress(@NonNull InterfaceAddress interfaceAddress) { + this(interfaceAddress.getAddress(), + interfaceAddress.getNetworkPrefixLength()); + } + + /** + * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or + * "2001:db8::1/64". The flags are set to zero and the scope is determined from the address. + * @param address The string to parse. + * @hide + */ + @SystemApi + public LinkAddress(@NonNull String address) { + this(address, 0, 0); + this.scope = scopeForUnicastAddress(this.address); + } + + /** + * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or + * "2001:db8::1/64", with the specified flags and scope. + * @param address The string to parse. + * @param flags The address flags. + * @param scope The address scope. + * @hide + */ + @SystemApi + public LinkAddress(@NonNull String address, int flags, int scope) { + // This may throw an IllegalArgumentException; catching it is the caller's responsibility. + // TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24". + Pair ipAndMask = NetworkUtils.parseIpAndMask(address); + init(ipAndMask.first, ipAndMask.second, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN); + } + + /** + * Returns a string representation of this address, such as "192.0.2.1/24" or "2001:db8::1/64". + * The string representation does not contain the flags and scope, just the address and prefix + * length. + */ + @Override + public String toString() { + return address.getHostAddress() + "/" + prefixLength; + } + + /** + * Compares this {@code LinkAddress} instance against {@code obj}. Two addresses are equal if + * their address, prefix length, flags and scope are equal. Thus, for example, two addresses + * that have the same address and prefix length are not equal if one of them is deprecated and + * the other is not. + * + * @param obj the object to be tested for equality. + * @return {@code true} if both objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LinkAddress)) { + return false; + } + LinkAddress linkAddress = (LinkAddress) obj; + return this.address.equals(linkAddress.address) + && this.prefixLength == linkAddress.prefixLength + && this.flags == linkAddress.flags + && this.scope == linkAddress.scope + && this.deprecationTime == linkAddress.deprecationTime + && this.expirationTime == linkAddress.expirationTime; + } + + /** + * Returns a hashcode for this address. + */ + @Override + public int hashCode() { + return Objects.hash(address, prefixLength, flags, scope, deprecationTime, expirationTime); + } + + /** + * Determines whether this {@code LinkAddress} and the provided {@code LinkAddress} + * represent the same address. Two {@code LinkAddresses} represent the same address + * if they have the same IP address and prefix length, even if their properties are + * different. + * + * @param other the {@code LinkAddress} to compare to. + * @return {@code true} if both objects have the same address and prefix length, {@code false} + * otherwise. + * @hide + */ + @SystemApi + public boolean isSameAddressAs(@Nullable LinkAddress other) { + if (other == null) { + return false; + } + return address.equals(other.address) && prefixLength == other.prefixLength; + } + + /** + * Returns the {@link InetAddress} of this {@code LinkAddress}. + */ + public InetAddress getAddress() { + return address; + } + + /** + * Returns the prefix length of this {@code LinkAddress}. + */ + @IntRange(from = 0, to = 128) + public int getPrefixLength() { + return prefixLength; + } + + /** + * Returns the prefix length of this {@code LinkAddress}. + * TODO: Delete all callers and remove in favour of getPrefixLength(). + * @hide + */ + @UnsupportedAppUsage + @IntRange(from = 0, to = 128) + public int getNetworkPrefixLength() { + return getPrefixLength(); + } + + /** + * Returns the flags of this {@code LinkAddress}. + */ + public int getFlags() { + int flags = this.flags; + if (deprecationTime != LIFETIME_UNKNOWN) { + if (SystemClock.elapsedRealtime() >= deprecationTime) { + flags |= IFA_F_DEPRECATED; + } else { + // If deprecation time is in the future, or permanent. + flags &= ~IFA_F_DEPRECATED; + } + } + + if (expirationTime == LIFETIME_PERMANENT) { + flags |= IFA_F_PERMANENT; + } else if (expirationTime != LIFETIME_UNKNOWN) { + // If we know this address expired or will expire in the future, then this address + // should not be permanent. + flags &= ~IFA_F_PERMANENT; + } + + // Do no touch the original flags. Return the adjusted flags here. + return flags; + } + + /** + * Returns the scope of this {@code LinkAddress}. + */ + public int getScope() { + return scope; + } + + /** + * Get the deprecation time, as reported by {@link SystemClock#elapsedRealtime}, when this + * {@link LinkAddress} will be or was deprecated. At the time existing connections can still use + * this address until it expires, but new connections should use the new address. + * + * @return The deprecation time in milliseconds. {@link #LIFETIME_UNKNOWN} indicates this + * information is not available. {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} + * will never be deprecated. + * + * @hide + */ + @SystemApi + public long getDeprecationTime() { + return deprecationTime; + } + + /** + * Get the expiration time, as reported by {@link SystemClock#elapsedRealtime}, when this + * {@link LinkAddress} will expire and be removed from the interface. + * + * @return The expiration time in milliseconds. {@link #LIFETIME_UNKNOWN} indicates this + * information is not available. {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} + * will never expire. + * + * @hide + */ + @SystemApi + public long getExpirationTime() { + return expirationTime; + } + + /** + * Returns true if this {@code LinkAddress} is global scope and preferred (i.e., not currently + * deprecated). + * + * @hide + */ + @SystemApi + public boolean isGlobalPreferred() { + /** + * Note that addresses flagged as IFA_F_OPTIMISTIC are + * simultaneously flagged as IFA_F_TENTATIVE (when the tentative + * state has cleared either DAD has succeeded or failed, and both + * flags are cleared regardless). + */ + int flags = getFlags(); + return (scope == RT_SCOPE_UNIVERSE + && !isIpv6ULA() + && (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L + && ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L)); + } + + /** + * Implement the Parcelable interface. + */ + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(address.getAddress()); + dest.writeInt(prefixLength); + dest.writeInt(this.flags); + dest.writeInt(scope); + dest.writeLong(deprecationTime); + dest.writeLong(expirationTime); + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public LinkAddress createFromParcel(Parcel in) { + InetAddress address = null; + try { + address = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + // Nothing we can do here. When we call the constructor, we'll throw an + // IllegalArgumentException, because a LinkAddress can't have a null + // InetAddress. + } + int prefixLength = in.readInt(); + int flags = in.readInt(); + int scope = in.readInt(); + long deprecationTime = in.readLong(); + long expirationTime = in.readLong(); + return new LinkAddress(address, prefixLength, flags, scope, deprecationTime, + expirationTime); + } + + public LinkAddress[] newArray(int size) { + return new LinkAddress[size]; + } + }; +} diff --git a/framework/src/android/net/LinkProperties.aidl b/framework/src/android/net/LinkProperties.aidl new file mode 100644 index 0000000000..a8b3c7b039 --- /dev/null +++ b/framework/src/android/net/LinkProperties.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright (C) 2010 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; + +@JavaOnlyStableParcelable parcelable LinkProperties; diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java new file mode 100644 index 0000000000..e41ed72b25 --- /dev/null +++ b/framework/src/android/net/LinkProperties.java @@ -0,0 +1,1823 @@ +/* + * Copyright (C) 2010 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.net.module.util.LinkPropertiesUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; + +/** + * Describes the properties of a network link. + * + * A link represents a connection to a network. + * It may have multiple addresses and multiple gateways, + * multiple dns servers but only one http proxy and one + * network interface. + * + * Note that this is just a holder of data. Modifying it + * does not affect live networks. + * + */ +public final class LinkProperties implements Parcelable { + // The interface described by the network link. + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private String mIfaceName; + private final ArrayList mLinkAddresses = new ArrayList<>(); + private final ArrayList mDnses = new ArrayList<>(); + // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service. + private final ArrayList mPcscfs = new ArrayList(); + private final ArrayList mValidatedPrivateDnses = new ArrayList<>(); + private boolean mUsePrivateDns; + private String mPrivateDnsServerName; + private String mDomains; + private ArrayList mRoutes = new ArrayList<>(); + private Inet4Address mDhcpServerAddress; + private ProxyInfo mHttpProxy; + private int mMtu; + // in the format "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max" + private String mTcpBufferSizes; + private IpPrefix mNat64Prefix; + private boolean mWakeOnLanSupported; + private Uri mCaptivePortalApiUrl; + private CaptivePortalData mCaptivePortalData; + + /** + * Indicates whether parceling should preserve fields that are set based on permissions of + * the process receiving the {@link LinkProperties}. + */ + private final transient boolean mParcelSensitiveFields; + + private static final int MIN_MTU = 68; + + private static final int MIN_MTU_V6 = 1280; + + private static final int MAX_MTU = 10000; + + private static final int INET6_ADDR_LENGTH = 16; + + // Stores the properties of links that are "stacked" above this link. + // Indexed by interface name to allow modification and to prevent duplicates being added. + private Hashtable mStackedLinks = new Hashtable<>(); + + /** + * @hide + */ + @UnsupportedAppUsage(implicitMember = + "values()[Landroid/net/LinkProperties$ProvisioningChange;") + public enum ProvisioningChange { + @UnsupportedAppUsage + STILL_NOT_PROVISIONED, + @UnsupportedAppUsage + LOST_PROVISIONING, + @UnsupportedAppUsage + GAINED_PROVISIONING, + @UnsupportedAppUsage + STILL_PROVISIONED, + } + + /** + * Compare the provisioning states of two LinkProperties instances. + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static ProvisioningChange compareProvisioning( + LinkProperties before, LinkProperties after) { + if (before.isProvisioned() && after.isProvisioned()) { + // On dual-stack networks, DHCPv4 renewals can occasionally fail. + // When this happens, IPv6-reachable services continue to function + // normally but IPv4-only services (naturally) fail. + // + // When an application using an IPv4-only service reports a bad + // network condition to the framework, attempts to re-validate + // the network succeed (since we support IPv6-only networks) and + // nothing is changed. + // + // For users, this is confusing and unexpected behaviour, and is + // not necessarily easy to diagnose. Therefore, we treat changing + // from a dual-stack network to an IPv6-only network equivalent to + // a total loss of provisioning. + // + // For one such example of this, see b/18867306. + // + // Additionally, losing IPv6 provisioning can result in TCP + // connections getting stuck until timeouts fire and other + // baffling failures. Therefore, loss of either IPv4 or IPv6 on a + // previously dual-stack network is deemed a lost of provisioning. + if ((before.isIpv4Provisioned() && !after.isIpv4Provisioned()) + || (before.isIpv6Provisioned() && !after.isIpv6Provisioned())) { + return ProvisioningChange.LOST_PROVISIONING; + } + return ProvisioningChange.STILL_PROVISIONED; + } else if (before.isProvisioned() && !after.isProvisioned()) { + return ProvisioningChange.LOST_PROVISIONING; + } else if (!before.isProvisioned() && after.isProvisioned()) { + return ProvisioningChange.GAINED_PROVISIONING; + } else { // !before.isProvisioned() && !after.isProvisioned() + return ProvisioningChange.STILL_NOT_PROVISIONED; + } + } + + /** + * Constructs a new {@code LinkProperties} with default values. + */ + public LinkProperties() { + mParcelSensitiveFields = false; + } + + /** + * @hide + */ + @SystemApi + public LinkProperties(@Nullable LinkProperties source) { + this(source, false /* parcelSensitiveFields */); + } + + /** + * Create a copy of a {@link LinkProperties} that may preserve fields that were set + * based on the permissions of the process that originally received it. + * + *

By default {@link LinkProperties} does not preserve such fields during parceling, as + * they should not be shared outside of the process that receives them without appropriate + * checks. + * @param parcelSensitiveFields Whether the sensitive fields should be kept when parceling + * @hide + */ + @SystemApi + public LinkProperties(@Nullable LinkProperties source, boolean parcelSensitiveFields) { + mParcelSensitiveFields = parcelSensitiveFields; + if (source == null) return; + mIfaceName = source.mIfaceName; + mLinkAddresses.addAll(source.mLinkAddresses); + mDnses.addAll(source.mDnses); + mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses); + mUsePrivateDns = source.mUsePrivateDns; + mPrivateDnsServerName = source.mPrivateDnsServerName; + mPcscfs.addAll(source.mPcscfs); + mDomains = source.mDomains; + mRoutes.addAll(source.mRoutes); + mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy); + for (LinkProperties l: source.mStackedLinks.values()) { + addStackedLink(l); + } + setMtu(source.mMtu); + setDhcpServerAddress(source.getDhcpServerAddress()); + mTcpBufferSizes = source.mTcpBufferSizes; + mNat64Prefix = source.mNat64Prefix; + mWakeOnLanSupported = source.mWakeOnLanSupported; + mCaptivePortalApiUrl = source.mCaptivePortalApiUrl; + mCaptivePortalData = source.mCaptivePortalData; + } + + /** + * Sets the interface name for this link. All {@link RouteInfo} already set for this + * will have their interface changed to match this new value. + * + * @param iface The name of the network interface used for this link. + */ + public void setInterfaceName(@Nullable String iface) { + mIfaceName = iface; + ArrayList newRoutes = new ArrayList<>(mRoutes.size()); + for (RouteInfo route : mRoutes) { + newRoutes.add(routeWithInterface(route)); + } + mRoutes = newRoutes; + } + + /** + * Gets the interface name for this link. May be {@code null} if not set. + * + * @return The interface name set for this link or {@code null}. + */ + public @Nullable String getInterfaceName() { + return mIfaceName; + } + + /** + * @hide + */ + @SystemApi + public @NonNull List getAllInterfaceNames() { + List interfaceNames = new ArrayList<>(mStackedLinks.size() + 1); + if (mIfaceName != null) interfaceNames.add(mIfaceName); + for (LinkProperties stacked: mStackedLinks.values()) { + interfaceNames.addAll(stacked.getAllInterfaceNames()); + } + return interfaceNames; + } + + /** + * Returns all the addresses on this link. We often think of a link having a single address, + * however, particularly with Ipv6 several addresses are typical. Note that the + * {@code LinkProperties} actually contains {@link LinkAddress} objects which also include + * prefix lengths for each address. This is a simplified utility alternative to + * {@link LinkProperties#getLinkAddresses}. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for this link. + * @hide + */ + @SystemApi + public @NonNull List getAddresses() { + final List addresses = new ArrayList<>(); + for (LinkAddress linkAddress : mLinkAddresses) { + addresses.add(linkAddress.getAddress()); + } + return Collections.unmodifiableList(addresses); + } + + /** + * Returns all the addresses on this link and all the links stacked above it. + * @hide + */ + @UnsupportedAppUsage + public @NonNull List getAllAddresses() { + List addresses = new ArrayList<>(); + for (LinkAddress linkAddress : mLinkAddresses) { + addresses.add(linkAddress.getAddress()); + } + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllAddresses()); + } + return addresses; + } + + private int findLinkAddressIndex(LinkAddress address) { + for (int i = 0; i < mLinkAddresses.size(); i++) { + if (mLinkAddresses.get(i).isSameAddressAs(address)) { + return i; + } + } + return -1; + } + + /** + * Adds a {@link LinkAddress} to this {@code LinkProperties} if a {@link LinkAddress} of the + * same address/prefix does not already exist. If it does exist it is replaced. + * @param address The {@code LinkAddress} to add. + * @return true if {@code address} was added or updated, false otherwise. + * @hide + */ + @SystemApi + public boolean addLinkAddress(@NonNull LinkAddress address) { + if (address == null) { + return false; + } + int i = findLinkAddressIndex(address); + if (i < 0) { + // Address was not present. Add it. + mLinkAddresses.add(address); + return true; + } else if (mLinkAddresses.get(i).equals(address)) { + // Address was present and has same properties. Do nothing. + return false; + } else { + // Address was present and has different properties. Update it. + mLinkAddresses.set(i, address); + return true; + } + } + + /** + * Removes a {@link LinkAddress} from this {@code LinkProperties}. Specifically, matches + * and {@link LinkAddress} with the same address and prefix. + * + * @param toRemove A {@link LinkAddress} specifying the address to remove. + * @return true if the address was removed, false if it did not exist. + * @hide + */ + @SystemApi + public boolean removeLinkAddress(@NonNull LinkAddress toRemove) { + int i = findLinkAddressIndex(toRemove); + if (i >= 0) { + mLinkAddresses.remove(i); + return true; + } + return false; + } + + /** + * Returns all the {@link LinkAddress} on this link. Typically a link will have + * one IPv4 address and one or more IPv6 addresses. + * + * @return An unmodifiable {@link List} of {@link LinkAddress} for this link. + */ + public @NonNull List getLinkAddresses() { + return Collections.unmodifiableList(mLinkAddresses); + } + + /** + * Returns all the addresses on this link and all the links stacked above it. + * @hide + */ + @SystemApi + public @NonNull List getAllLinkAddresses() { + List addresses = new ArrayList<>(mLinkAddresses); + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllLinkAddresses()); + } + return addresses; + } + + /** + * Replaces the {@link LinkAddress} in this {@code LinkProperties} with + * the given {@link Collection} of {@link LinkAddress}. + * + * @param addresses The {@link Collection} of {@link LinkAddress} to set in this + * object. + */ + public void setLinkAddresses(@NonNull Collection addresses) { + mLinkAddresses.clear(); + for (LinkAddress address: addresses) { + addLinkAddress(address); + } + } + + /** + * Adds the given {@link InetAddress} to the list of DNS servers, if not present. + * + * @param dnsServer The {@link InetAddress} to add to the list of DNS servers. + * @return true if the DNS server was added, false if it was already present. + * @hide + */ + @SystemApi + public boolean addDnsServer(@NonNull InetAddress dnsServer) { + if (dnsServer != null && !mDnses.contains(dnsServer)) { + mDnses.add(dnsServer); + return true; + } + return false; + } + + /** + * Removes the given {@link InetAddress} from the list of DNS servers. + * + * @param dnsServer The {@link InetAddress} to remove from the list of DNS servers. + * @return true if the DNS server was removed, false if it did not exist. + * @hide + */ + @SystemApi + public boolean removeDnsServer(@NonNull InetAddress dnsServer) { + return mDnses.remove(dnsServer); + } + + /** + * Replaces the DNS servers in this {@code LinkProperties} with + * the given {@link Collection} of {@link InetAddress} objects. + * + * @param dnsServers The {@link Collection} of DNS servers to set in this object. + */ + public void setDnsServers(@NonNull Collection dnsServers) { + mDnses.clear(); + for (InetAddress dnsServer: dnsServers) { + addDnsServer(dnsServer); + } + } + + /** + * Returns all the {@link InetAddress} for DNS servers on this link. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for DNS servers on + * this link. + */ + public @NonNull List getDnsServers() { + return Collections.unmodifiableList(mDnses); + } + + /** + * Set whether private DNS is currently in use on this network. + * + * @param usePrivateDns The private DNS state. + * @hide + */ + @SystemApi + public void setUsePrivateDns(boolean usePrivateDns) { + mUsePrivateDns = usePrivateDns; + } + + /** + * Returns whether private DNS is currently in use on this network. When + * private DNS is in use, applications must not send unencrypted DNS + * queries as doing so could reveal private user information. Furthermore, + * if private DNS is in use and {@link #getPrivateDnsServerName} is not + * {@code null}, DNS queries must be sent to the specified DNS server. + * + * @return {@code true} if private DNS is in use, {@code false} otherwise. + */ + public boolean isPrivateDnsActive() { + return mUsePrivateDns; + } + + /** + * Set the name of the private DNS server to which private DNS queries + * should be sent when in strict mode. This value should be {@code null} + * when private DNS is off or in opportunistic mode. + * + * @param privateDnsServerName The private DNS server name. + * @hide + */ + @SystemApi + public void setPrivateDnsServerName(@Nullable String privateDnsServerName) { + mPrivateDnsServerName = privateDnsServerName; + } + + /** + * Set DHCP server address. + * + * @param serverAddress the server address to set. + */ + public void setDhcpServerAddress(@Nullable Inet4Address serverAddress) { + mDhcpServerAddress = serverAddress; + } + + /** + * Get DHCP server address + * + * @return The current DHCP server address. + */ + public @Nullable Inet4Address getDhcpServerAddress() { + return mDhcpServerAddress; + } + + /** + * Returns the private DNS server name that is in use. If not {@code null}, + * private DNS is in strict mode. In this mode, applications should ensure + * that all DNS queries are encrypted and sent to this hostname and that + * queries are only sent if the hostname's certificate is valid. If + * {@code null} and {@link #isPrivateDnsActive} is {@code true}, private + * DNS is in opportunistic mode, and applications should ensure that DNS + * queries are encrypted and sent to a DNS server returned by + * {@link #getDnsServers}. System DNS will handle each of these cases + * correctly, but applications implementing their own DNS lookups must make + * sure to follow these requirements. + * + * @return The private DNS server name. + */ + public @Nullable String getPrivateDnsServerName() { + return mPrivateDnsServerName; + } + + /** + * Adds the given {@link InetAddress} to the list of validated private DNS servers, + * if not present. This is distinct from the server name in that these are actually + * resolved addresses. + * + * @param dnsServer The {@link InetAddress} to add to the list of validated private DNS servers. + * @return true if the DNS server was added, false if it was already present. + * @hide + */ + public boolean addValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) { + if (dnsServer != null && !mValidatedPrivateDnses.contains(dnsServer)) { + mValidatedPrivateDnses.add(dnsServer); + return true; + } + return false; + } + + /** + * Removes the given {@link InetAddress} from the list of validated private DNS servers. + * + * @param dnsServer The {@link InetAddress} to remove from the list of validated private DNS + * servers. + * @return true if the DNS server was removed, false if it did not exist. + * @hide + */ + public boolean removeValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) { + return mValidatedPrivateDnses.remove(dnsServer); + } + + /** + * Replaces the validated private DNS servers in this {@code LinkProperties} with + * the given {@link Collection} of {@link InetAddress} objects. + * + * @param dnsServers The {@link Collection} of validated private DNS servers to set in this + * object. + * @hide + */ + @SystemApi + public void setValidatedPrivateDnsServers(@NonNull Collection dnsServers) { + mValidatedPrivateDnses.clear(); + for (InetAddress dnsServer: dnsServers) { + addValidatedPrivateDnsServer(dnsServer); + } + } + + /** + * Returns all the {@link InetAddress} for validated private DNS servers on this link. + * These are resolved from the private DNS server name. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for validated private + * DNS servers on this link. + * @hide + */ + @SystemApi + public @NonNull List getValidatedPrivateDnsServers() { + return Collections.unmodifiableList(mValidatedPrivateDnses); + } + + /** + * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present. + * + * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers. + * @return true if the PCSCF server was added, false otherwise. + * @hide + */ + @SystemApi + public boolean addPcscfServer(@NonNull InetAddress pcscfServer) { + if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) { + mPcscfs.add(pcscfServer); + return true; + } + return false; + } + + /** + * Removes the given {@link InetAddress} from the list of PCSCF servers. + * + * @param pcscfServer The {@link InetAddress} to remove from the list of PCSCF servers. + * @return true if the PCSCF server was removed, false otherwise. + * @hide + */ + public boolean removePcscfServer(@NonNull InetAddress pcscfServer) { + return mPcscfs.remove(pcscfServer); + } + + /** + * Replaces the PCSCF servers in this {@code LinkProperties} with + * the given {@link Collection} of {@link InetAddress} objects. + * + * @param pcscfServers The {@link Collection} of PCSCF servers to set in this object. + * @hide + */ + @SystemApi + public void setPcscfServers(@NonNull Collection pcscfServers) { + mPcscfs.clear(); + for (InetAddress pcscfServer: pcscfServers) { + addPcscfServer(pcscfServer); + } + } + + /** + * Returns all the {@link InetAddress} for PCSCF servers on this link. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on + * this link. + * @hide + */ + @SystemApi + public @NonNull List getPcscfServers() { + return Collections.unmodifiableList(mPcscfs); + } + + /** + * Sets the DNS domain search path used on this link. + * + * @param domains A {@link String} listing in priority order the comma separated + * domains to search when resolving host names on this link. + */ + public void setDomains(@Nullable String domains) { + mDomains = domains; + } + + /** + * Get the DNS domains search path set for this link. May be {@code null} if not set. + * + * @return A {@link String} containing the comma separated domains to search when resolving host + * names on this link or {@code null}. + */ + public @Nullable String getDomains() { + return mDomains; + } + + /** + * Sets the Maximum Transmission Unit size to use on this link. This should not be used + * unless the system default (1500) is incorrect. Values less than 68 or greater than + * 10000 will be ignored. + * + * @param mtu The MTU to use for this link. + */ + public void setMtu(int mtu) { + mMtu = mtu; + } + + /** + * Gets any non-default MTU size set for this link. Note that if the default is being used + * this will return 0. + * + * @return The mtu value set for this link. + */ + public int getMtu() { + return mMtu; + } + + /** + * Sets the tcp buffers sizes to be used when this link is the system default. + * Should be of the form "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max". + * + * @param tcpBufferSizes The tcp buffers sizes to use. + * + * @hide + */ + @SystemApi + public void setTcpBufferSizes(@Nullable String tcpBufferSizes) { + mTcpBufferSizes = tcpBufferSizes; + } + + /** + * Gets the tcp buffer sizes. May be {@code null} if not set. + * + * @return the tcp buffer sizes to use when this link is the system default or {@code null}. + * + * @hide + */ + @SystemApi + public @Nullable String getTcpBufferSizes() { + return mTcpBufferSizes; + } + + private RouteInfo routeWithInterface(RouteInfo route) { + return new RouteInfo( + route.getDestination(), + route.getGateway(), + mIfaceName, + route.getType(), + route.getMtu()); + } + + private int findRouteIndexByRouteKey(RouteInfo route) { + for (int i = 0; i < mRoutes.size(); i++) { + if (mRoutes.get(i).getRouteKey().equals(route.getRouteKey())) { + return i; + } + } + return -1; + } + + /** + * Adds a {@link RouteInfo} to this {@code LinkProperties}, if a {@link RouteInfo} + * with the same {@link RouteInfo.RouteKey} with different properties + * (e.g., different MTU), it will be updated. If the {@link RouteInfo} had an + * interface name set and that differs from the interface set for this + * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. + * The proper course is to add either un-named or properly named {@link RouteInfo}. + * + * @param route A {@link RouteInfo} to add to this object. + * @return {@code true} was added or updated, false otherwise. + */ + public boolean addRoute(@NonNull RouteInfo route) { + String routeIface = route.getInterface(); + if (routeIface != null && !routeIface.equals(mIfaceName)) { + throw new IllegalArgumentException( + "Route added with non-matching interface: " + routeIface + + " vs. " + mIfaceName); + } + route = routeWithInterface(route); + + int i = findRouteIndexByRouteKey(route); + if (i == -1) { + // Route was not present. Add it. + mRoutes.add(route); + return true; + } else if (mRoutes.get(i).equals(route)) { + // Route was present and has same properties. Do nothing. + return false; + } else { + // Route was present and has different properties. Update it. + mRoutes.set(i, route); + return true; + } + } + + /** + * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must + * specify an interface and the interface must match the interface of this + * {@code LinkProperties}, or it will not be removed. + * + * @param route A {@link RouteInfo} specifying the route to remove. + * @return {@code true} if the route was removed, {@code false} if it was not present. + * + * @hide + */ + @SystemApi + public boolean removeRoute(@NonNull RouteInfo route) { + return Objects.equals(mIfaceName, route.getInterface()) && mRoutes.remove(route); + } + + /** + * Returns all the {@link RouteInfo} set on this link. + * + * @return An unmodifiable {@link List} of {@link RouteInfo} for this link. + */ + public @NonNull List getRoutes() { + return Collections.unmodifiableList(mRoutes); + } + + /** + * Make sure this LinkProperties instance contains routes that cover the local subnet + * of its link addresses. Add any route that is missing. + * @hide + */ + public void ensureDirectlyConnectedRoutes() { + for (LinkAddress addr : mLinkAddresses) { + addRoute(new RouteInfo(addr, null, mIfaceName)); + } + } + + /** + * Returns all the routes on this link and all the links stacked above it. + * @hide + */ + @SystemApi + public @NonNull List getAllRoutes() { + List routes = new ArrayList<>(mRoutes); + for (LinkProperties stacked: mStackedLinks.values()) { + routes.addAll(stacked.getAllRoutes()); + } + return routes; + } + + /** + * Sets the recommended {@link ProxyInfo} to use on this link, or {@code null} for none. + * Note that Http Proxies are only a hint - the system recommends their use, but it does + * not enforce it and applications may ignore them. + * + * @param proxy A {@link ProxyInfo} defining the HTTP Proxy to use on this link. + */ + public void setHttpProxy(@Nullable ProxyInfo proxy) { + mHttpProxy = proxy; + } + + /** + * Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link. + * + * @return The {@link ProxyInfo} set on this link or {@code null}. + */ + public @Nullable ProxyInfo getHttpProxy() { + return mHttpProxy; + } + + /** + * Returns the NAT64 prefix in use on this link, if any. + * + * @return the NAT64 prefix or {@code null}. + */ + public @Nullable IpPrefix getNat64Prefix() { + return mNat64Prefix; + } + + /** + * Sets the NAT64 prefix in use on this link. + * + * Currently, only 96-bit prefixes (i.e., where the 32-bit IPv4 address is at the end of the + * 128-bit IPv6 address) are supported or {@code null} for no prefix. + * + * @param prefix the NAT64 prefix. + */ + public void setNat64Prefix(@Nullable IpPrefix prefix) { + if (prefix != null && prefix.getPrefixLength() != 96) { + throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix); + } + mNat64Prefix = prefix; // IpPrefix objects are immutable. + } + + /** + * Adds a stacked link. + * + * If there is already a stacked link with the same interface name as link, + * that link is replaced with link. Otherwise, link is added to the list + * of stacked links. + * + * @param link The link to add. + * @return true if the link was stacked, false otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean addStackedLink(@NonNull LinkProperties link) { + if (link.getInterfaceName() != null) { + mStackedLinks.put(link.getInterfaceName(), link); + return true; + } + return false; + } + + /** + * Removes a stacked link. + * + * If there is a stacked link with the given interface name, it is + * removed. Otherwise, nothing changes. + * + * @param iface The interface name of the link to remove. + * @return true if the link was removed, false otherwise. + * @hide + */ + public boolean removeStackedLink(@NonNull String iface) { + LinkProperties removed = mStackedLinks.remove(iface); + return removed != null; + } + + /** + * Returns all the links stacked on top of this link. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public @NonNull List getStackedLinks() { + if (mStackedLinks.isEmpty()) { + return Collections.emptyList(); + } + final List stacked = new ArrayList<>(); + for (LinkProperties link : mStackedLinks.values()) { + stacked.add(new LinkProperties(link)); + } + return Collections.unmodifiableList(stacked); + } + + /** + * Clears this object to its initial state. + */ + public void clear() { + if (mParcelSensitiveFields) { + throw new UnsupportedOperationException( + "Cannot clear LinkProperties when parcelSensitiveFields is set"); + } + + mIfaceName = null; + mLinkAddresses.clear(); + mDnses.clear(); + mUsePrivateDns = false; + mPrivateDnsServerName = null; + mPcscfs.clear(); + mDomains = null; + mRoutes.clear(); + mHttpProxy = null; + mStackedLinks.clear(); + mMtu = 0; + mDhcpServerAddress = null; + mTcpBufferSizes = null; + mNat64Prefix = null; + mWakeOnLanSupported = false; + mCaptivePortalApiUrl = null; + mCaptivePortalData = null; + } + + /** + * Implement the Parcelable interface + */ + public int describeContents() { + return 0; + } + + @Override + public String toString() { + // Space as a separator, so no need for spaces at start/end of the individual fragments. + final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}"); + + if (mIfaceName != null) { + resultJoiner.add("InterfaceName:"); + resultJoiner.add(mIfaceName); + } + + resultJoiner.add("LinkAddresses: ["); + if (!mLinkAddresses.isEmpty()) { + resultJoiner.add(TextUtils.join(",", mLinkAddresses)); + } + resultJoiner.add("]"); + + resultJoiner.add("DnsAddresses: ["); + if (!mDnses.isEmpty()) { + resultJoiner.add(TextUtils.join(",", mDnses)); + } + resultJoiner.add("]"); + + if (mUsePrivateDns) { + resultJoiner.add("UsePrivateDns: true"); + } + + if (mPrivateDnsServerName != null) { + resultJoiner.add("PrivateDnsServerName:"); + resultJoiner.add(mPrivateDnsServerName); + } + + if (!mPcscfs.isEmpty()) { + resultJoiner.add("PcscfAddresses: ["); + resultJoiner.add(TextUtils.join(",", mPcscfs)); + resultJoiner.add("]"); + } + + if (!mValidatedPrivateDnses.isEmpty()) { + final StringJoiner validatedPrivateDnsesJoiner = + new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]"); + for (final InetAddress addr : mValidatedPrivateDnses) { + validatedPrivateDnsesJoiner.add(addr.getHostAddress()); + } + resultJoiner.add(validatedPrivateDnsesJoiner.toString()); + } + + resultJoiner.add("Domains:"); + resultJoiner.add(mDomains); + + resultJoiner.add("MTU:"); + resultJoiner.add(Integer.toString(mMtu)); + + if (mWakeOnLanSupported) { + resultJoiner.add("WakeOnLanSupported: true"); + } + + if (mDhcpServerAddress != null) { + resultJoiner.add("ServerAddress:"); + resultJoiner.add(mDhcpServerAddress.toString()); + } + + if (mCaptivePortalApiUrl != null) { + resultJoiner.add("CaptivePortalApiUrl: " + mCaptivePortalApiUrl); + } + + if (mCaptivePortalData != null) { + resultJoiner.add("CaptivePortalData: " + mCaptivePortalData); + } + + if (mTcpBufferSizes != null) { + resultJoiner.add("TcpBufferSizes:"); + resultJoiner.add(mTcpBufferSizes); + } + + resultJoiner.add("Routes: ["); + if (!mRoutes.isEmpty()) { + resultJoiner.add(TextUtils.join(",", mRoutes)); + } + resultJoiner.add("]"); + + if (mHttpProxy != null) { + resultJoiner.add("HttpProxy:"); + resultJoiner.add(mHttpProxy.toString()); + } + + if (mNat64Prefix != null) { + resultJoiner.add("Nat64Prefix:"); + resultJoiner.add(mNat64Prefix.toString()); + } + + final Collection stackedLinksValues = mStackedLinks.values(); + if (!stackedLinksValues.isEmpty()) { + final StringJoiner stackedLinksJoiner = new StringJoiner(",", "Stacked: [", "]"); + for (final LinkProperties lp : stackedLinksValues) { + stackedLinksJoiner.add("[ " + lp + " ]"); + } + resultJoiner.add(stackedLinksJoiner.toString()); + } + + return resultJoiner.toString(); + } + + /** + * Returns true if this link has an IPv4 address. + * + * @return {@code true} if there is an IPv4 address, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv4Address() { + for (LinkAddress address : mLinkAddresses) { + if (address.getAddress() instanceof Inet4Address) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv4 address, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv4Address() { + return hasIpv4Address(); + } + + /** + * Returns true if this link or any of its stacked interfaces has an IPv4 address. + * + * @return {@code true} if there is an IPv4 address, {@code false} otherwise. + */ + private boolean hasIpv4AddressOnInterface(String iface) { + // mIfaceName can be null. + return (Objects.equals(iface, mIfaceName) && hasIpv4Address()) + || (iface != null && mStackedLinks.containsKey(iface) + && mStackedLinks.get(iface).hasIpv4Address()); + } + + /** + * Returns true if this link has a global preferred IPv6 address. + * + * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasGlobalIpv6Address() { + for (LinkAddress address : mLinkAddresses) { + if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) { + return true; + } + } + return false; + } + + /** + * Returns true if this link has an IPv4 unreachable default route. + * + * @return {@code true} if there is an IPv4 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv4UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv4UnreachableDefault()) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasGlobalIPv6Address() { + return hasGlobalIpv6Address(); + } + + /** + * Returns true if this link has an IPv4 default route. + * + * @return {@code true} if there is an IPv4 default route, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv4DefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv4Default()) { + return true; + } + } + return false; + } + + /** + * Returns true if this link has an IPv6 unreachable default route. + * + * @return {@code true} if there is an IPv6 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv6UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv6UnreachableDefault()) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv4 default route, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv4DefaultRoute() { + return hasIpv4DefaultRoute(); + } + + /** + * Returns true if this link has an IPv6 default route. + * + * @return {@code true} if there is an IPv6 default route, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv6DefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv6Default()) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv6 default route, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv6DefaultRoute() { + return hasIpv6DefaultRoute(); + } + + /** + * Returns true if this link has an IPv4 DNS server. + * + * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv4DnsServer() { + for (InetAddress ia : mDnses) { + if (ia instanceof Inet4Address) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv4DnsServer() { + return hasIpv4DnsServer(); + } + + /** + * Returns true if this link has an IPv6 DNS server. + * + * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv6DnsServer() { + for (InetAddress ia : mDnses) { + if (ia instanceof Inet6Address) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv6DnsServer() { + return hasIpv6DnsServer(); + } + + /** + * Returns true if this link has an IPv4 PCSCF server. + * + * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise. + * @hide + */ + public boolean hasIpv4PcscfServer() { + for (InetAddress ia : mPcscfs) { + if (ia instanceof Inet4Address) { + return true; + } + } + return false; + } + + /** + * Returns true if this link has an IPv6 PCSCF server. + * + * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise. + * @hide + */ + public boolean hasIpv6PcscfServer() { + for (InetAddress ia : mPcscfs) { + if (ia instanceof Inet6Address) { + return true; + } + } + return false; + } + + /** + * Returns true if this link is provisioned for global IPv4 connectivity. + * This requires an IP address, default route, and DNS server. + * + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isIpv4Provisioned() { + return (hasIpv4Address() + && hasIpv4DefaultRoute() + && hasIpv4DnsServer()); + } + + /** + * Returns true if this link is provisioned for global IPv6 connectivity. + * This requires an IP address, default route, and DNS server. + * + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isIpv6Provisioned() { + return (hasGlobalIpv6Address() + && hasIpv6DefaultRoute() + && hasIpv6DnsServer()); + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean isIPv6Provisioned() { + return isIpv6Provisioned(); + } + + + /** + * Returns true if this link is provisioned for global connectivity, + * for at least one Internet Protocol family. + * + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isProvisioned() { + return (isIpv4Provisioned() || isIpv6Provisioned()); + } + + /** + * Evaluate whether the {@link InetAddress} is considered reachable. + * + * @return {@code true} if the given {@link InetAddress} is considered reachable, + * {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isReachable(@NonNull InetAddress ip) { + final List allRoutes = getAllRoutes(); + // If we don't have a route to this IP address, it's not reachable. + final RouteInfo bestRoute = RouteInfo.selectBestRoute(allRoutes, ip); + if (bestRoute == null) { + return false; + } + + // TODO: better source address evaluation for destination addresses. + + if (ip instanceof Inet4Address) { + // For IPv4, it suffices for now to simply have any address. + return hasIpv4AddressOnInterface(bestRoute.getInterface()); + } else if (ip instanceof Inet6Address) { + if (ip.isLinkLocalAddress()) { + // For now, just make sure link-local destinations have + // scopedIds set, since transmits will generally fail otherwise. + // TODO: verify it matches the ifindex of one of the interfaces. + return (((Inet6Address)ip).getScopeId() != 0); + } else { + // For non-link-local destinations check that either the best route + // is directly connected or that some global preferred address exists. + // TODO: reconsider all cases (disconnected ULA networks, ...). + return (!bestRoute.hasGateway() || hasGlobalIpv6Address()); + } + } + + return false; + } + + /** + * Compares this {@code LinkProperties} interface name against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalInterfaceName(target, this); + } + + /** + * Compares this {@code LinkProperties} DHCP server address against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalDhcpServerAddress(@NonNull LinkProperties target) { + return Objects.equals(mDhcpServerAddress, target.mDhcpServerAddress); + } + + /** + * Compares this {@code LinkProperties} interface addresses against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalAddresses(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalAddresses(target, this); + } + + /** + * Compares this {@code LinkProperties} DNS addresses against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalDnses(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalDnses(target, this); + } + + /** + * Compares this {@code LinkProperties} private DNS settings against the + * target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalPrivateDns(@NonNull LinkProperties target) { + return (isPrivateDnsActive() == target.isPrivateDnsActive() + && TextUtils.equals(getPrivateDnsServerName(), + target.getPrivateDnsServerName())); + } + + /** + * Compares this {@code LinkProperties} validated private DNS addresses against + * the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalValidatedPrivateDnses(@NonNull LinkProperties target) { + Collection targetDnses = target.getValidatedPrivateDnsServers(); + return (mValidatedPrivateDnses.size() == targetDnses.size()) + ? mValidatedPrivateDnses.containsAll(targetDnses) : false; + } + + /** + * Compares this {@code LinkProperties} PCSCF addresses against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalPcscfs(@NonNull LinkProperties target) { + Collection targetPcscfs = target.getPcscfServers(); + return (mPcscfs.size() == targetPcscfs.size()) ? + mPcscfs.containsAll(targetPcscfs) : false; + } + + /** + * Compares this {@code LinkProperties} Routes against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalRoutes(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalRoutes(target, this); + } + + /** + * Compares this {@code LinkProperties} HttpProxy against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalHttpProxy(target, this); + } + + /** + * Compares this {@code LinkProperties} stacked links against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public boolean isIdenticalStackedLinks(@NonNull LinkProperties target) { + if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) { + return false; + } + for (LinkProperties stacked : mStackedLinks.values()) { + // Hashtable values can never be null. + String iface = stacked.getInterfaceName(); + if (!stacked.equals(target.mStackedLinks.get(iface))) { + return false; + } + } + return true; + } + + /** + * Compares this {@code LinkProperties} MTU against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalMtu(@NonNull LinkProperties target) { + return getMtu() == target.getMtu(); + } + + /** + * Compares this {@code LinkProperties} Tcp buffer sizes against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalTcpBufferSizes(@NonNull LinkProperties target) { + return Objects.equals(mTcpBufferSizes, target.mTcpBufferSizes); + } + + /** + * Compares this {@code LinkProperties} NAT64 prefix against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalNat64Prefix(@NonNull LinkProperties target) { + return Objects.equals(mNat64Prefix, target.mNat64Prefix); + } + + /** + * Compares this {@code LinkProperties} WakeOnLan supported against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalWakeOnLan(LinkProperties target) { + return isWakeOnLanSupported() == target.isWakeOnLanSupported(); + } + + /** + * Compares this {@code LinkProperties}'s CaptivePortalApiUrl against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalCaptivePortalApiUrl(LinkProperties target) { + return Objects.equals(mCaptivePortalApiUrl, target.mCaptivePortalApiUrl); + } + + /** + * Compares this {@code LinkProperties}'s CaptivePortalData against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalCaptivePortalData(LinkProperties target) { + return Objects.equals(mCaptivePortalData, target.mCaptivePortalData); + } + + /** + * Set whether the network interface supports WakeOnLAN + * + * @param supported WakeOnLAN supported value + * + * @hide + */ + public void setWakeOnLanSupported(boolean supported) { + mWakeOnLanSupported = supported; + } + + /** + * Returns whether the network interface supports WakeOnLAN + * + * @return {@code true} if interface supports WakeOnLAN, {@code false} otherwise. + */ + public boolean isWakeOnLanSupported() { + return mWakeOnLanSupported; + } + + /** + * Set the URL of the captive portal API endpoint to get more information about the network. + * @hide + */ + @SystemApi + public void setCaptivePortalApiUrl(@Nullable Uri url) { + mCaptivePortalApiUrl = url; + } + + /** + * Get the URL of the captive portal API endpoint to get more information about the network. + * + *

This is null unless the application has + * {@link android.Manifest.permission.NETWORK_SETTINGS} or + * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions, and the network provided + * the URL. + * @hide + */ + @SystemApi + @Nullable + public Uri getCaptivePortalApiUrl() { + return mCaptivePortalApiUrl; + } + + /** + * Set the CaptivePortalData obtained from the captive portal API (RFC7710bis). + * @hide + */ + @SystemApi + public void setCaptivePortalData(@Nullable CaptivePortalData data) { + mCaptivePortalData = data; + } + + /** + * Get the CaptivePortalData obtained from the captive portal API (RFC7710bis). + * + *

This is null unless the application has + * {@link android.Manifest.permission.NETWORK_SETTINGS} or + * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions. + * @hide + */ + @SystemApi + @Nullable + public CaptivePortalData getCaptivePortalData() { + return mCaptivePortalData; + } + + /** + * Compares this {@code LinkProperties} instance against the target + * LinkProperties in {@code obj}. Two LinkPropertieses are equal if + * all their fields are equal in values. + * + * For collection fields, such as mDnses, containsAll() is used to check + * if two collections contains the same elements, independent of order. + * There are two thoughts regarding containsAll() + * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal. + * 2. Worst case performance is O(n^2). + * + * @param obj the object to be tested for equality. + * @return {@code true} if both objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + + if (!(obj instanceof LinkProperties)) return false; + + LinkProperties target = (LinkProperties) obj; + /* + * This method does not check that stacked interfaces are equal, because + * stacked interfaces are not so much a property of the link as a + * description of connections between links. + */ + return isIdenticalInterfaceName(target) + && isIdenticalAddresses(target) + && isIdenticalDhcpServerAddress(target) + && isIdenticalDnses(target) + && isIdenticalPrivateDns(target) + && isIdenticalValidatedPrivateDnses(target) + && isIdenticalPcscfs(target) + && isIdenticalRoutes(target) + && isIdenticalHttpProxy(target) + && isIdenticalStackedLinks(target) + && isIdenticalMtu(target) + && isIdenticalTcpBufferSizes(target) + && isIdenticalNat64Prefix(target) + && isIdenticalWakeOnLan(target) + && isIdenticalCaptivePortalApiUrl(target) + && isIdenticalCaptivePortalData(target); + } + + /** + * Generate hashcode based on significant fields + * + * Equal objects must produce the same hash code, while unequal objects + * may have the same hash codes. + */ + @Override + public int hashCode() { + return ((null == mIfaceName) ? 0 : mIfaceName.hashCode() + + mLinkAddresses.size() * 31 + + mDnses.size() * 37 + + mValidatedPrivateDnses.size() * 61 + + ((null == mDomains) ? 0 : mDomains.hashCode()) + + mRoutes.size() * 41 + + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()) + + mStackedLinks.hashCode() * 47) + + mMtu * 51 + + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode()) + + (mUsePrivateDns ? 57 : 0) + + ((null == mDhcpServerAddress) ? 0 : mDhcpServerAddress.hashCode()) + + mPcscfs.size() * 67 + + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode()) + + Objects.hash(mNat64Prefix) + + (mWakeOnLanSupported ? 71 : 0) + + Objects.hash(mCaptivePortalApiUrl, mCaptivePortalData); + } + + /** + * Implement the Parcelable interface. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getInterfaceName()); + dest.writeInt(mLinkAddresses.size()); + for (LinkAddress linkAddress : mLinkAddresses) { + dest.writeParcelable(linkAddress, flags); + } + + writeAddresses(dest, mDnses); + writeAddresses(dest, mValidatedPrivateDnses); + dest.writeBoolean(mUsePrivateDns); + dest.writeString(mPrivateDnsServerName); + writeAddresses(dest, mPcscfs); + dest.writeString(mDomains); + writeAddress(dest, mDhcpServerAddress); + dest.writeInt(mMtu); + dest.writeString(mTcpBufferSizes); + dest.writeInt(mRoutes.size()); + for (RouteInfo route : mRoutes) { + dest.writeParcelable(route, flags); + } + + if (mHttpProxy != null) { + dest.writeByte((byte)1); + dest.writeParcelable(mHttpProxy, flags); + } else { + dest.writeByte((byte)0); + } + dest.writeParcelable(mNat64Prefix, 0); + + ArrayList stackedLinks = new ArrayList<>(mStackedLinks.values()); + dest.writeList(stackedLinks); + + dest.writeBoolean(mWakeOnLanSupported); + dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalApiUrl : null, 0); + dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalData : null, 0); + } + + private static void writeAddresses(@NonNull Parcel dest, @NonNull List list) { + dest.writeInt(list.size()); + for (InetAddress d : list) { + writeAddress(dest, d); + } + } + + private static void writeAddress(@NonNull Parcel dest, @Nullable InetAddress addr) { + byte[] addressBytes = (addr == null ? null : addr.getAddress()); + dest.writeByteArray(addressBytes); + if (addr instanceof Inet6Address) { + final Inet6Address v6Addr = (Inet6Address) addr; + final boolean hasScopeId = v6Addr.getScopeId() != 0; + dest.writeBoolean(hasScopeId); + if (hasScopeId) dest.writeInt(v6Addr.getScopeId()); + } + } + + @Nullable + private static InetAddress readAddress(@NonNull Parcel p) throws UnknownHostException { + final byte[] addr = p.createByteArray(); + if (addr == null) return null; + + if (addr.length == INET6_ADDR_LENGTH) { + final boolean hasScopeId = p.readBoolean(); + final int scopeId = hasScopeId ? p.readInt() : 0; + return Inet6Address.getByAddress(null /* host */, addr, scopeId); + } + + return InetAddress.getByAddress(addr); + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public LinkProperties createFromParcel(Parcel in) { + LinkProperties netProp = new LinkProperties(); + + String iface = in.readString(); + if (iface != null) { + netProp.setInterfaceName(iface); + } + int addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + netProp.addLinkAddress(in.readParcelable(null)); + } + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + try { + netProp.addDnsServer(readAddress(in)); + } catch (UnknownHostException e) { } + } + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + try { + netProp.addValidatedPrivateDnsServer(readAddress(in)); + } catch (UnknownHostException e) { } + } + netProp.setUsePrivateDns(in.readBoolean()); + netProp.setPrivateDnsServerName(in.readString()); + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + try { + netProp.addPcscfServer(readAddress(in)); + } catch (UnknownHostException e) { } + } + netProp.setDomains(in.readString()); + try { + netProp.setDhcpServerAddress((Inet4Address) InetAddress + .getByAddress(in.createByteArray())); + } catch (UnknownHostException e) { } + netProp.setMtu(in.readInt()); + netProp.setTcpBufferSizes(in.readString()); + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + netProp.addRoute(in.readParcelable(null)); + } + if (in.readByte() == 1) { + netProp.setHttpProxy(in.readParcelable(null)); + } + netProp.setNat64Prefix(in.readParcelable(null)); + ArrayList stackedLinks = new ArrayList(); + in.readList(stackedLinks, LinkProperties.class.getClassLoader()); + for (LinkProperties stackedLink: stackedLinks) { + netProp.addStackedLink(stackedLink); + } + netProp.setWakeOnLanSupported(in.readBoolean()); + + netProp.setCaptivePortalApiUrl(in.readParcelable(null)); + netProp.setCaptivePortalData(in.readParcelable(null)); + return netProp; + } + + public LinkProperties[] newArray(int size) { + return new LinkProperties[size]; + } + }; + + /** + * Check the valid MTU range based on IPv4 or IPv6. + * @hide + */ + public static boolean isValidMtu(int mtu, boolean ipv6) { + if (ipv6) { + return mtu >= MIN_MTU_V6 && mtu <= MAX_MTU; + } else { + return mtu >= MIN_MTU && mtu <= MAX_MTU; + } + } +} diff --git a/framework/src/android/net/MacAddress.aidl b/framework/src/android/net/MacAddress.aidl new file mode 100644 index 0000000000..48a18a7ac8 --- /dev/null +++ b/framework/src/android/net/MacAddress.aidl @@ -0,0 +1,20 @@ +/** + * + * 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; + +@JavaOnlyStableParcelable parcelable MacAddress; diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java new file mode 100644 index 0000000000..c83c23a4b6 --- /dev/null +++ b/framework/src/android/net/MacAddress.java @@ -0,0 +1,400 @@ +/* + * Copyright 2017 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 android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.net.wifi.WifiInfo; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; +import com.android.net.module.util.MacAddressUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.Inet6Address; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.Arrays; + +/** + * Representation of a MAC address. + * + * This class only supports 48 bits long addresses and does not support 64 bits long addresses. + * Instances of this class are immutable. This class provides implementations of hashCode() + * and equals() that make it suitable for use as keys in standard implementations of + * {@link java.util.Map}. + */ +public final class MacAddress implements Parcelable { + + private static final int ETHER_ADDR_LEN = 6; + private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + + /** + * The MacAddress representing the unique broadcast MAC address. + */ + public static final MacAddress BROADCAST_ADDRESS = MacAddress.fromBytes(ETHER_ADDR_BROADCAST); + + /** + * The MacAddress zero MAC address. + * + *

Not publicly exposed or treated specially since the OUI 00:00:00 is registered. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0); + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_UNKNOWN, + TYPE_UNICAST, + TYPE_MULTICAST, + TYPE_BROADCAST, + }) + public @interface MacAddressType { } + + /** @hide Indicates a MAC address of unknown type. */ + public static final int TYPE_UNKNOWN = 0; + /** Indicates a MAC address is a unicast address. */ + public static final int TYPE_UNICAST = 1; + /** Indicates a MAC address is a multicast address. */ + public static final int TYPE_MULTICAST = 2; + /** Indicates a MAC address is the broadcast address. */ + public static final int TYPE_BROADCAST = 3; + + private static final long VALID_LONG_MASK = (1L << 48) - 1; + private static final long LOCALLY_ASSIGNED_MASK = MacAddress.fromString("2:0:0:0:0:0").mAddr; + private static final long MULTICAST_MASK = MacAddress.fromString("1:0:0:0:0:0").mAddr; + private static final long OUI_MASK = MacAddress.fromString("ff:ff:ff:0:0:0").mAddr; + private static final long NIC_MASK = MacAddress.fromString("0:0:0:ff:ff:ff").mAddr; + private static final MacAddress BASE_GOOGLE_MAC = MacAddress.fromString("da:a1:19:0:0:0"); + /** Default wifi MAC address used for a special purpose **/ + private static final MacAddress DEFAULT_MAC_ADDRESS = + MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); + + // Internal representation of the MAC address as a single 8 byte long. + // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the + // MAC address are encoded in the 6 least significant bytes of the long, where the first + // byte of the array is mapped to the 3rd highest logical byte of the long, the second + // byte of the array is mapped to the 4th highest logical byte of the long, and so on. + private final long mAddr; + + private MacAddress(long addr) { + mAddr = (VALID_LONG_MASK & addr); + } + + /** + * Returns the type of this address. + * + * @return the int constant representing the MAC address type of this MacAddress. + */ + public @MacAddressType int getAddressType() { + if (equals(BROADCAST_ADDRESS)) { + return TYPE_BROADCAST; + } + if ((mAddr & MULTICAST_MASK) != 0) { + return TYPE_MULTICAST; + } + return TYPE_UNICAST; + } + + /** + * @return true if this MacAddress is a locally assigned address. + */ + public boolean isLocallyAssigned() { + return (mAddr & LOCALLY_ASSIGNED_MASK) != 0; + } + + /** + * Convert this MacAddress to a byte array. + * + * The returned array is in network order. For example, if this MacAddress is 1:2:3:4:5:6, + * the returned array is [1, 2, 3, 4, 5, 6]. + * + * @return a byte array representation of this MacAddress. + */ + public @NonNull byte[] toByteArray() { + return byteAddrFromLongAddr(mAddr); + } + + /** + * Returns a human-readable representation of this MacAddress. + * The exact format is implementation-dependent and should not be assumed to have any + * particular format. + */ + @Override + public @NonNull String toString() { + return stringAddrFromLongAddr(mAddr); + } + + /** + * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal + * numbers in [0,ff] joined by ':' characters. + */ + public @NonNull String toOuiString() { + return String.format( + "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff); + } + + @Override + public int hashCode() { + return (int) ((mAddr >> 32) ^ mAddr); + } + + @Override + public boolean equals(@Nullable Object o) { + return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mAddr); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public MacAddress createFromParcel(Parcel in) { + return new MacAddress(in.readLong()); + } + + public MacAddress[] newArray(int size) { + return new MacAddress[size]; + } + }; + + /** + * Returns true if the given byte array is an valid MAC address. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array. + * @return true if the given byte array is not null and has the length of a MAC address. + * + * @hide + */ + public static boolean isMacAddress(byte[] addr) { + return MacAddressUtils.isMacAddress(addr); + } + + /** + * Returns the MAC address type of the MAC address represented by the given byte array, + * or null if the given byte array does not represent a MAC address. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array representing a MAC address. + * @return the int constant representing the MAC address type of the MAC address represented + * by the given byte array, or type UNKNOWN if the byte array is not a valid MAC address. + * + * @hide + */ + public static int macAddressType(byte[] addr) { + if (!isMacAddress(addr)) { + return TYPE_UNKNOWN; + } + return MacAddress.fromBytes(addr).getAddressType(); + } + + /** + * Converts a String representation of a MAC address to a byte array representation. + * A valid String representation for a MacAddress is a series of 6 values in the + * range [0,ff] printed in hexadecimal and joined by ':' characters. + * + * @param addr a String representation of a MAC address. + * @return the byte representation of the MAC address. + * @throws IllegalArgumentException if the given String is not a valid representation. + * + * @hide + */ + public static @NonNull byte[] byteAddrFromStringAddr(String addr) { + Preconditions.checkNotNull(addr); + String[] parts = addr.split(":"); + if (parts.length != ETHER_ADDR_LEN) { + throw new IllegalArgumentException(addr + " was not a valid MAC address"); + } + byte[] bytes = new byte[ETHER_ADDR_LEN]; + for (int i = 0; i < ETHER_ADDR_LEN; i++) { + int x = Integer.valueOf(parts[i], 16); + if (x < 0 || 0xff < x) { + throw new IllegalArgumentException(addr + "was not a valid MAC address"); + } + bytes[i] = (byte) x; + } + return bytes; + } + + /** + * Converts a byte array representation of a MAC address to a String representation made + * of 6 hexadecimal numbers in [0,ff] joined by ':' characters. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array representation of a MAC address. + * @return the String representation of the MAC address. + * @throws IllegalArgumentException if the given byte array is not a valid representation. + * + * @hide + */ + public static @NonNull String stringAddrFromByteAddr(byte[] addr) { + if (!isMacAddress(addr)) { + return null; + } + return String.format("%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + } + + private static byte[] byteAddrFromLongAddr(long addr) { + return MacAddressUtils.byteAddrFromLongAddr(addr); + } + + private static long longAddrFromByteAddr(byte[] addr) { + return MacAddressUtils.longAddrFromByteAddr(addr); + } + + // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr)) + // that avoids the allocation of an intermediary byte[]. + private static long longAddrFromStringAddr(String addr) { + Preconditions.checkNotNull(addr); + String[] parts = addr.split(":"); + if (parts.length != ETHER_ADDR_LEN) { + throw new IllegalArgumentException(addr + " was not a valid MAC address"); + } + long longAddr = 0; + for (int i = 0; i < parts.length; i++) { + int x = Integer.valueOf(parts[i], 16); + if (x < 0 || 0xff < x) { + throw new IllegalArgumentException(addr + "was not a valid MAC address"); + } + longAddr = x + (longAddr << 8); + } + return longAddr; + } + + // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr)) + // that avoids the allocation of an intermediary byte[]. + private static @NonNull String stringAddrFromLongAddr(long addr) { + return String.format("%02x:%02x:%02x:%02x:%02x:%02x", + (addr >> 40) & 0xff, + (addr >> 32) & 0xff, + (addr >> 24) & 0xff, + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + addr & 0xff); + } + + /** + * Creates a MacAddress from the given String representation. A valid String representation + * for a MacAddress is a series of 6 values in the range [0,ff] printed in hexadecimal + * and joined by ':' characters. + * + * @param addr a String representation of a MAC address. + * @return the MacAddress corresponding to the given String representation. + * @throws IllegalArgumentException if the given String is not a valid representation. + */ + public static @NonNull MacAddress fromString(@NonNull String addr) { + return new MacAddress(longAddrFromStringAddr(addr)); + } + + /** + * Creates a MacAddress from the given byte array representation. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array representation of a MAC address. + * @return the MacAddress corresponding to the given byte array representation. + * @throws IllegalArgumentException if the given byte array is not a valid representation. + */ + public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) { + return new MacAddress(longAddrFromByteAddr(addr)); + } + + /** + * Returns a generated MAC address whose 24 least significant bits constituting the + * NIC part of the address are randomly selected and has Google OUI base. + * + * The locally assigned bit is always set to 1. The multicast bit is always set to 0. + * + * @return a random locally assigned, unicast MacAddress with Google OUI. + * + * @hide + */ + public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() { + return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom()); + } + + // Convenience function for working around the lack of byte literals. + private static byte[] addr(int... in) { + if (in.length != ETHER_ADDR_LEN) { + throw new IllegalArgumentException(Arrays.toString(in) + + " was not an array with length equal to " + ETHER_ADDR_LEN); + } + byte[] out = new byte[ETHER_ADDR_LEN]; + for (int i = 0; i < ETHER_ADDR_LEN; i++) { + out[i] = (byte) in[i]; + } + return out; + } + + /** + * Checks if this MAC Address matches the provided range. + * + * @param baseAddress MacAddress representing the base address to compare with. + * @param mask MacAddress representing the mask to use during comparison. + * @return true if this MAC Address matches the given range. + * + */ + public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) { + Preconditions.checkNotNull(baseAddress); + Preconditions.checkNotNull(mask); + return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr); + } + + /** + * Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address is converted + * to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is used to construct a link-local + * IPv6 address per RFC 4862. + * + * @return A link-local Inet6Address constructed from the MAC address. + */ + public @Nullable Inet6Address getLinkLocalIpv6FromEui48Mac() { + byte[] macEui48Bytes = toByteArray(); + byte[] addr = new byte[16]; + + addr[0] = (byte) 0xfe; + addr[1] = (byte) 0x80; + addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit + addr[9] = macEui48Bytes[1]; + addr[10] = macEui48Bytes[2]; + addr[11] = (byte) 0xff; + addr[12] = (byte) 0xfe; + addr[13] = macEui48Bytes[3]; + addr[14] = macEui48Bytes[4]; + addr[15] = macEui48Bytes[5]; + + try { + return Inet6Address.getByAddress(null, addr, 0); + } catch (UnknownHostException e) { + return null; + } + } +} diff --git a/framework/src/android/net/NattKeepalivePacketData.java b/framework/src/android/net/NattKeepalivePacketData.java new file mode 100644 index 0000000000..c4f8fc281f --- /dev/null +++ b/framework/src/android/net/NattKeepalivePacketData.java @@ -0,0 +1,144 @@ +/* + * 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 android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS; +import static android.net.InvalidPacketException.ERROR_INVALID_PORT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.system.OsConstants; + +import com.android.net.module.util.IpUtils; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** @hide */ +@SystemApi +public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable { + private static final int IPV4_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + + // This should only be constructed via static factory methods, such as + // nattKeepalivePacket + public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort, + @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws + InvalidPacketException { + super(srcAddress, srcPort, dstAddress, dstPort, data); + } + + /** + * Factory method to create Nat-T keepalive packet structure. + * @hide + */ + public static NattKeepalivePacketData nattKeepalivePacket( + InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) + throws InvalidPacketException { + + if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + if (dstPort != NattSocketKeepalive.NATT_PORT) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + + int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort((short) 0x4500); // IP version and TOS + buf.putShort((short) length); + buf.putInt(0); // ID, flags, offset + buf.put((byte) 64); // TTL + buf.put((byte) OsConstants.IPPROTO_UDP); + int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum + buf.put(srcAddress.getAddress()); + buf.put(dstAddress.getAddress()); + buf.putShort((short) srcPort); + buf.putShort((short) dstPort); + buf.putShort((short) (length - 20)); // UDP length + int udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum + buf.put((byte) 0xff); // NAT-T keepalive + buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); + buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); + + return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); + } + + /** Parcelable Implementation */ + public int describeContents() { + return 0; + } + + /** Write to parcel */ + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(getSrcAddress().getHostAddress()); + out.writeString(getDstAddress().getHostAddress()); + out.writeInt(getSrcPort()); + out.writeInt(getDstPort()); + } + + /** Parcelable Creator */ + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public NattKeepalivePacketData createFromParcel(Parcel in) { + final InetAddress srcAddress = + InetAddresses.parseNumericAddress(in.readString()); + final InetAddress dstAddress = + InetAddresses.parseNumericAddress(in.readString()); + final int srcPort = in.readInt(); + final int dstPort = in.readInt(); + try { + return NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort, + dstAddress, dstPort); + } catch (InvalidPacketException e) { + throw new IllegalArgumentException( + "Invalid NAT-T keepalive data: " + e.getError()); + } + } + + public NattKeepalivePacketData[] newArray(int size) { + return new NattKeepalivePacketData[size]; + } + }; + + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof NattKeepalivePacketData)) return false; + final NattKeepalivePacketData other = (NattKeepalivePacketData) o; + final InetAddress srcAddress = getSrcAddress(); + final InetAddress dstAddress = getDstAddress(); + return srcAddress.equals(other.getSrcAddress()) + && dstAddress.equals(other.getDstAddress()) + && getSrcPort() == other.getSrcPort() + && getDstPort() == other.getDstPort(); + } + + @Override + public int hashCode() { + return Objects.hash(getSrcAddress(), getDstAddress(), getSrcPort(), getDstPort()); + } +} diff --git a/framework/src/android/net/NattSocketKeepalive.java b/framework/src/android/net/NattSocketKeepalive.java new file mode 100644 index 0000000000..a15d165e65 --- /dev/null +++ b/framework/src/android/net/NattSocketKeepalive.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 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 android.annotation.NonNull; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; + +import java.net.InetAddress; +import java.util.concurrent.Executor; + +/** @hide */ +public final class NattSocketKeepalive extends SocketKeepalive { + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + @NonNull private final InetAddress mSource; + @NonNull private final InetAddress mDestination; + private final int mResourceId; + + NattSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + int resourceId, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, pfd, executor, callback); + mSource = source; + mDestination = destination; + mResourceId = resourceId; + } + + @Override + void startImpl(int intervalSec) { + mExecutor.execute(() -> { + try { + mService.startNattKeepaliveWithFd(mNetwork, mPfd, mResourceId, + intervalSec, mCallback, + mSource.getHostAddress(), mDestination.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting socket keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } + + @Override + void stopImpl() { + mExecutor.execute(() -> { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping socket keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } +} diff --git a/framework/src/android/net/Network.aidl b/framework/src/android/net/Network.aidl new file mode 100644 index 0000000000..05622025bf --- /dev/null +++ b/framework/src/android/net/Network.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright (C) 2014 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; + +@JavaOnlyStableParcelable parcelable Network; diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java new file mode 100644 index 0000000000..46141e0d0c --- /dev/null +++ b/framework/src/android/net/Network.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2014 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.okhttp.internalandroidapi.Dns; +import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.net.SocketFactory; + +/** + * Identifies a {@code Network}. This is supplied to applications via + * {@link ConnectivityManager.NetworkCallback} in response to the active + * {@link ConnectivityManager#requestNetwork} or passive + * {@link ConnectivityManager#registerNetworkCallback} calls. + * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis + * through a targeted {@link SocketFactory} or process-wide via + * {@link ConnectivityManager#bindProcessToNetwork}. + */ +public class Network implements Parcelable { + + /** + * The unique id of the network. + * @hide + */ + @UnsupportedAppUsage + public final int netId; + + // Objects used to perform per-network operations such as getSocketFactory + // and openConnection, and a lock to protect access to them. + private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null; + // mUrlConnectionFactory is initialized lazily when it is first needed. + @GuardedBy("mLock") + private HttpURLConnectionFactory mUrlConnectionFactory; + private final Object mLock = new Object(); + + // Default connection pool values. These are evaluated at startup, just + // like the OkHttp code. Also like the OkHttp code, we will throw parse + // exceptions at class loading time if the properties are set but are not + // valid integers. + private static final boolean httpKeepAlive = + Boolean.parseBoolean(System.getProperty("http.keepAlive", "true")); + private static final int httpMaxConnections = + httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0; + private static final long httpKeepAliveDurationMs = + Long.parseLong(System.getProperty("http.keepAliveDuration", "300000")); // 5 minutes. + // Value used to obfuscate network handle longs. + // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // value in the native/android/net.c NDK implementation. + private static final long HANDLE_MAGIC = 0xcafed00dL; + private static final int HANDLE_MAGIC_SIZE = 32; + + // A boolean to control how getAllByName()/getByName() behaves in the face + // of Private DNS. + // + // When true, these calls will request that DNS resolution bypass any + // Private DNS that might otherwise apply. Use of this feature is restricted + // and permission checks are made by netd (attempts to bypass Private DNS + // without appropriate permission are silently turned into vanilla DNS + // requests). This only affects DNS queries made using this network object. + // + // It it not parceled to receivers because (a) it can be set or cleared at + // anytime and (b) receivers should be explicit about attempts to bypass + // Private DNS so that the intent of the code is easily determined and + // code search audits are possible. + private final transient boolean mPrivateDnsBypass; + + /** + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public Network(int netId) { + this(netId, false); + } + + /** + * @hide + */ + public Network(int netId, boolean privateDnsBypass) { + this.netId = netId; + this.mPrivateDnsBypass = privateDnsBypass; + } + + /** + * @hide + */ + @SystemApi + public Network(@NonNull Network that) { + this(that.netId, that.mPrivateDnsBypass); + } + + /** + * Operates the same as {@code InetAddress.getAllByName} except that host + * resolution is done on this network. + * + * @param host the hostname or literal IP string to be resolved. + * @return the array of addresses associated with the specified host. + * @throws UnknownHostException if the address lookup fails. + */ + public InetAddress[] getAllByName(String host) throws UnknownHostException { + return InetAddress.getAllByNameOnNet(host, getNetIdForResolv()); + } + + /** + * Operates the same as {@code InetAddress.getByName} except that host + * resolution is done on this network. + * + * @param host the hostname to be resolved to an address or {@code null}. + * @return the {@code InetAddress} instance representing the host. + * @throws UnknownHostException + * if the address lookup fails. + */ + public InetAddress getByName(String host) throws UnknownHostException { + return InetAddress.getByNameOnNet(host, getNetIdForResolv()); + } + + /** + * Obtain a Network object for which Private DNS is to be bypassed when attempting + * to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given + * instance for hostname resolution. + * + * @hide + */ + @SystemApi + public @NonNull Network getPrivateDnsBypassingCopy() { + return new Network(netId, true); + } + + /** + * Get the unique id of the network. + * + * @hide + */ + @SystemApi + public int getNetId() { + return netId; + } + + /** + * Returns a netid marked with the Private DNS bypass flag. + * + * This flag must be kept in sync with the NETID_USE_LOCAL_NAMESERVERS flag + * in system/netd/include/NetdClient.h. + * + * @hide + */ + public int getNetIdForResolv() { + return mPrivateDnsBypass + ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag. + : netId; + } + + /** + * A {@code SocketFactory} that produces {@code Socket}'s bound to this network. + */ + private class NetworkBoundSocketFactory extends SocketFactory { + private Socket connectToHost(String host, int port, SocketAddress localAddress) + throws IOException { + // Lookup addresses only on this Network. + InetAddress[] hostAddresses = getAllByName(host); + // Try all addresses. + for (int i = 0; i < hostAddresses.length; i++) { + try { + Socket socket = createSocket(); + boolean failed = true; + try { + if (localAddress != null) socket.bind(localAddress); + socket.connect(new InetSocketAddress(hostAddresses[i], port)); + failed = false; + return socket; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + } catch (IOException e) { + if (i == (hostAddresses.length - 1)) throw e; + } + } + throw new UnknownHostException(host); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException { + return connectToHost(host, port, new InetSocketAddress(localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, + int localPort) throws IOException { + Socket socket = createSocket(); + boolean failed = true; + try { + socket.bind(new InetSocketAddress(localAddress, localPort)); + socket.connect(new InetSocketAddress(address, port)); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + return socket; + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + Socket socket = createSocket(); + boolean failed = true; + try { + socket.connect(new InetSocketAddress(host, port)); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + return socket; + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return connectToHost(host, port, null); + } + + @Override + public Socket createSocket() throws IOException { + Socket socket = new Socket(); + boolean failed = true; + try { + bindSocket(socket); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + return socket; + } + } + + /** + * Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by + * this factory will have its traffic sent over this {@code Network}. Note that if this + * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the + * past or future will cease to work. + * + * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this + * {@code Network}. + */ + public SocketFactory getSocketFactory() { + if (mNetworkBoundSocketFactory == null) { + synchronized (mLock) { + if (mNetworkBoundSocketFactory == null) { + mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(); + } + } + } + return mNetworkBoundSocketFactory; + } + + private static HttpURLConnectionFactory createUrlConnectionFactory(Dns dnsLookup) { + // Set configuration on the HttpURLConnectionFactory that will be good for all + // connections created by this Network. Configuration that might vary is left + // until openConnection() and passed as arguments. + HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory(); + urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup + // A private connection pool just for this Network. + urlConnectionFactory.setNewConnectionPool(httpMaxConnections, + httpKeepAliveDurationMs, TimeUnit.MILLISECONDS); + return urlConnectionFactory; + } + + /** + * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent + * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}. + * + * @return a {@code URLConnection} to the resource referred to by this URL. + * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS. + * @throws IOException if an error occurs while opening the connection. + * @see java.net.URL#openConnection() + */ + public URLConnection openConnection(URL url) throws IOException { + final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull(); + if (cm == null) { + throw new IOException("No ConnectivityManager yet constructed, please construct one"); + } + // TODO: Should this be optimized to avoid fetching the global proxy for every request? + final ProxyInfo proxyInfo = cm.getProxyForNetwork(this); + final java.net.Proxy proxy; + if (proxyInfo != null) { + proxy = proxyInfo.makeProxy(); + } else { + proxy = java.net.Proxy.NO_PROXY; + } + return openConnection(url, proxy); + } + + /** + * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent + * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}. + * + * @param proxy the proxy through which the connection will be established. + * @return a {@code URLConnection} to the resource referred to by this URL. + * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS. + * @throws IllegalArgumentException if the argument proxy is null. + * @throws IOException if an error occurs while opening the connection. + * @see java.net.URL#openConnection() + */ + public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException { + if (proxy == null) throw new IllegalArgumentException("proxy is null"); + // TODO: This creates a connection pool and host resolver for + // every Network object, instead of one for every NetId. This is + // suboptimal, because an app could potentially have more than one + // Network object for the same NetId, causing increased memory footprint + // and performance penalties due to lack of connection reuse (connection + // setup time, congestion window growth time, etc.). + // + // Instead, investigate only having one connection pool and host resolver + // for every NetId, perhaps by using a static HashMap of NetIds to + // connection pools and host resolvers. The tricky part is deciding when + // to remove a map entry; a WeakHashMap shouldn't be used because whether + // a Network is referenced doesn't correlate with whether a new Network + // will be instantiated in the near future with the same NetID. A good + // solution would involve purging empty (or when all connections are timed + // out) ConnectionPools. + final HttpURLConnectionFactory urlConnectionFactory; + synchronized (mLock) { + if (mUrlConnectionFactory == null) { + Dns dnsLookup = hostname -> Arrays.asList(getAllByName(hostname)); + mUrlConnectionFactory = createUrlConnectionFactory(dnsLookup); + } + urlConnectionFactory = mUrlConnectionFactory; + } + SocketFactory socketFactory = getSocketFactory(); + return urlConnectionFactory.openConnection(url, socketFactory, proxy); + } + + /** + * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the + * socket will be sent on this {@code Network}, irrespective of any process-wide network binding + * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be + * connected. + */ + public void bindSocket(DatagramSocket socket) throws IOException { + // Query a property of the underlying socket to ensure that the socket's file descriptor + // exists, is available to bind to a network and is not closed. + socket.getReuseAddress(); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); + } + + /** + * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket + * will be sent on this {@code Network}, irrespective of any process-wide network binding set by + * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected. + */ + public void bindSocket(Socket socket) throws IOException { + // Query a property of the underlying socket to ensure that the socket's file descriptor + // exists, is available to bind to a network and is not closed. + socket.getReuseAddress(); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); + } + + /** + * Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the + * socket represented by this file descriptor will be sent on this {@code Network}, + * irrespective of any process-wide network binding set by + * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected. + */ + public void bindSocket(FileDescriptor fd) throws IOException { + try { + final SocketAddress peer = Os.getpeername(fd); + final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress(); + if (!inetPeer.isAnyLocalAddress()) { + // Apparently, the kernel doesn't update a connected UDP socket's + // routing upon mark changes. + throw new SocketException("Socket is connected"); + } + } catch (ErrnoException e) { + // getpeername() failed. + if (e.errno != OsConstants.ENOTCONN) { + throw e.rethrowAsSocketException(); + } + } catch (ClassCastException e) { + // Wasn't an InetSocketAddress. + throw new SocketException("Only AF_INET/AF_INET6 sockets supported"); + } + + final int err = NetworkUtils.bindSocketToNetwork(fd, netId); + if (err != 0) { + // bindSocketToNetwork returns negative errno. + throw new ErrnoException("Binding socket to network " + netId, -err) + .rethrowAsSocketException(); + } + } + + /** + * Returns a {@link Network} object given a handle returned from {@link #getNetworkHandle}. + * + * @param networkHandle a handle returned from {@link #getNetworkHandle}. + * @return A {@link Network} object derived from {@code networkHandle}. + */ + public static Network fromNetworkHandle(long networkHandle) { + if (networkHandle == 0) { + throw new IllegalArgumentException( + "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network."); + } + if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC + || networkHandle < 0) { + throw new IllegalArgumentException( + "Value passed to fromNetworkHandle() is not a network handle."); + } + return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE)); + } + + /** + * Returns a handle representing this {@code Network}, for use with the NDK API. + */ + public long getNetworkHandle() { + // The network handle is explicitly not the same as the netId. + // + // The netId is an implementation detail which might be changed in the + // future, or which alone (i.e. in the absence of some additional + // context) might not be sufficient to fully identify a Network. + // + // As such, the intention is to prevent accidental misuse of the API + // that might result if a developer assumed that handles and netIds + // were identical and passing a netId to a call expecting a handle + // "just worked". Such accidental misuse, if widely deployed, might + // prevent future changes to the semantics of the netId field or + // inhibit the expansion of state required for Network objects. + // + // This extra layer of indirection might be seen as paranoia, and might + // never end up being necessary, but the added complexity is trivial. + // At some future date it may be desirable to realign the handle with + // Multiple Provisioning Domains API recommendations, as made by the + // IETF mif working group. + if (netId == 0) { + return 0L; // make this zero condition obvious for debugging + } + return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC; + } + + // implement the Parcelable interface + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(netId); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public Network createFromParcel(Parcel in) { + int netId = in.readInt(); + + return new Network(netId); + } + + public Network[] newArray(int size) { + return new Network[size]; + } + }; + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof Network)) return false; + Network other = (Network)obj; + return this.netId == other.netId; + } + + @Override + public int hashCode() { + return netId * 11; + } + + @Override + public String toString() { + return Integer.toString(netId); + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(NetworkProto.NET_ID, netId); + proto.end(token); + } +} diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java new file mode 100644 index 0000000000..d22d82d1f4 --- /dev/null +++ b/framework/src/android/net/NetworkAgent.java @@ -0,0 +1,1185 @@ +/* + * Copyright (C) 2014 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 android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.data.EpsBearerQosSessionAttributes; +import android.util.Log; + +import com.android.connectivity.aidl.INetworkAgent; +import com.android.connectivity.aidl.INetworkAgentRegistry; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Protocol; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A utility class for handling for communicating between bearer-specific + * code and ConnectivityService. + * + * An agent manages the life cycle of a network. A network starts its + * life cycle when {@link register} is called on NetworkAgent. The network + * is then connecting. When full L3 connectivity has been established, + * the agent should call {@link markConnected} to inform the system that + * this network is ready to use. When the network disconnects its life + * ends and the agent should call {@link unregister}, at which point the + * system will clean up and free resources. + * Any reconnection becomes a new logical network, so after a network + * is disconnected the agent cannot be used any more. Network providers + * should create a new NetworkAgent instance to handle new connections. + * + * A bearer may have more than one NetworkAgent if it can simultaneously + * support separate networks (IMS / Internet / MMS Apns on cellular, or + * perhaps connections with different SSID or P2P for Wi-Fi). + * + * This class supports methods to start and stop sending keepalive packets. + * Keepalive packets are typically sent at periodic intervals over a network + * with NAT when there is no other traffic to avoid the network forcefully + * closing the connection. NetworkAgents that manage technologies that + * have hardware support for keepalive should implement the related + * methods to save battery life. NetworkAgent that cannot get support + * without waking up the CPU should not, as this would be prohibitive in + * terms of battery - these agents should simply not override the related + * methods, which results in the implementation returning + * {@link SocketKeepalive.ERROR_UNSUPPORTED} as appropriate. + * + * Keepalive packets need to be sent at relatively frequent intervals + * (a few seconds to a few minutes). As the contents of keepalive packets + * depend on the current network status, hardware needs to be configured + * to send them and has a limited amount of memory to do so. The HAL + * formalizes this as slots that an implementation can configure to send + * the correct packets. Devices typically have a small number of slots + * per radio technology, and the specific number of slots for each + * technology is specified in configuration files. + * {@see SocketKeepalive} for details. + * + * @hide + */ +@SystemApi +public abstract class NetworkAgent { + /** + * The {@link Network} corresponding to this object. + */ + @Nullable + private volatile Network mNetwork; + + @Nullable + private volatile INetworkAgentRegistry mRegistry; + + private interface RegistryAction { + void execute(@NonNull INetworkAgentRegistry registry) throws RemoteException; + } + + private final Handler mHandler; + private final String LOG_TAG; + private static final boolean DBG = true; + private static final boolean VDBG = false; + private final ArrayList mPreConnectedQueue = new ArrayList<>(); + private volatile long mLastBwRefreshTime = 0; + private static final long BW_REFRESH_MIN_WIN_MS = 500; + private boolean mBandwidthUpdateScheduled = false; + private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false); + @NonNull + private NetworkInfo mNetworkInfo; + @NonNull + private final Object mRegisterLock = new Object(); + + /** + * The ID of the {@link NetworkProvider} that created this object, or + * {@link NetworkProvider#ID_NONE} if unknown. + * @hide + */ + public final int providerId; + + private static final int BASE = Protocol.BASE_NETWORK_AGENT; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform it of + * suspected connectivity problems on its network. The NetworkAgent + * should take steps to verify and correct connectivity. + * @hide + */ + public static final int CMD_SUSPECT_BAD = BASE; + + /** + * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to + * ConnectivityService to pass the current NetworkInfo (connection state). + * Sent when the NetworkInfo changes, mainly due to change of state. + * obj = NetworkInfo + * @hide + */ + public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * NetworkCapabilties. + * obj = NetworkCapabilities + * @hide + */ + public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * NetworkProperties. + * obj = NetworkProperties + * @hide + */ + public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; + + /** + * Centralize the place where base network score, and network score scaling, will be + * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE + * @hide + */ + public static final int WIFI_BASE_SCORE = 60; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * network score. + * arg1 = network score int + * @hide + */ + public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * list of underlying networks. + * obj = array of Network objects + * @hide + */ + public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent of the + * networks status - whether we could use the network or could not, due to + * either a bad network configuration (no internet link) or captive portal. + * + * arg1 = either {@code VALID_NETWORK} or {@code INVALID_NETWORK} + * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String} + * representing URL that Internet probe was redirect to, if it was redirected, + * or mapping to {@code null} otherwise. + * @hide + */ + public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7; + + + /** + * Network validation suceeded. + * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}. + */ + public static final int VALIDATION_STATUS_VALID = 1; + + /** + * Network validation was attempted and failed. This may be received more than once as + * subsequent validation attempts are made. + */ + public static final int VALIDATION_STATUS_NOT_VALID = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "VALIDATION_STATUS_" }, value = { + VALIDATION_STATUS_VALID, + VALIDATION_STATUS_NOT_VALID + }) + public @interface ValidationStatus {} + + // TODO: remove. + /** @hide */ + public static final int VALID_NETWORK = 1; + /** @hide */ + public static final int INVALID_NETWORK = 2; + + /** + * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}. + * @hide + */ + public static final String REDIRECT_URL_KEY = "redirect URL"; + + /** + * Sent by the NetworkAgent to ConnectivityService to indicate this network was + * explicitly selected. This should be sent before the NetworkInfo is marked + * CONNECTED so it can be given special treatment at that time. + * + * obj = boolean indicating whether to use this network even if unvalidated + * @hide + */ + public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent of + * whether the network should in the future be used even if not validated. + * This decision is made by the user, but it is the network transport's + * responsibility to remember it. + * + * arg1 = 1 if true, 0 if false + * @hide + */ + public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull + * the underlying network connection for updated bandwidth information. + * @hide + */ + public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10; + + /** + * Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent + * periodically on the given interval. + * + * arg1 = the hardware slot number of the keepalive to start + * arg2 = interval in seconds + * obj = KeepalivePacketData object describing the data to be sent + * + * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. + * @hide + */ + public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11; + + /** + * Requests that the specified keepalive packet be stopped. + * + * arg1 = hardware slot number of the keepalive to stop. + * + * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. + * @hide + */ + public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12; + + /** + * Sent by the NetworkAgent to ConnectivityService to provide status on a socket keepalive + * request. This may either be the reply to a CMD_START_SOCKET_KEEPALIVE, or an asynchronous + * error notification. + * + * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive}, + * so that the app's {@link SocketKeepalive.Callback} methods can be called. + * + * arg1 = hardware slot number of the keepalive + * arg2 = error code + * @hide + */ + public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13; + + /** + * Sent by ConnectivityService to inform this network transport of signal strength thresholds + * that when crossed should trigger a system wakeup and a NetworkCapabilities update. + * + * obj = int[] describing signal strength thresholds. + * @hide + */ + public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14; + + /** + * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid + * automatically reconnecting to this network (e.g. via autojoin). Happens + * when user selects "No" option on the "Stay connected?" dialog box. + * @hide + */ + public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15; + + /** + * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter. + * + * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the + * remote site will send ACK packets in response to the keepalive packets, the firmware also + * needs to be configured to properly filter the ACKs to prevent the system from waking up. + * This does not happen with UDP, so this message is TCP-specific. + * arg1 = hardware slot number of the keepalive to filter for. + * obj = the keepalive packet to send repeatedly. + * @hide + */ + public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16; + + /** + * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See + * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}. + * arg1 = hardware slot number of the keepalive packet filter to remove. + * @hide + */ + public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17; + + /** + * Sent by ConnectivityService to the NetworkAgent to complete the bidirectional connection. + * obj = INetworkAgentRegistry + */ + private static final int EVENT_AGENT_CONNECTED = BASE + 18; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent that it was disconnected. + */ + private static final int EVENT_AGENT_DISCONNECTED = BASE + 19; + + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to register a new filter with + * callback. + * + * arg1 = QoS agent callback ID + * obj = {@link QosFilter} + * @hide + */ + public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20; + + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to unregister a callback. + * + * arg1 = QoS agent callback ID + * @hide + */ + public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21; + + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { + // The subtype can be changed with (TODO) setLegacySubtype, but it starts + // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. + final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, ""); + ni.setIsAvailable(true); + ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */, + config.getLegacyExtraInfo()); + return ni; + } + + /** + * Create a new network agent. + * @param context a {@link Context} to get system services from. + * @param looper the {@link Looper} on which to invoke the callbacks. + * @param logTag the tag for logs + * @param nc the initial {@link NetworkCapabilities} of this network. Update with + * sendNetworkCapabilities. + * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties. + * @param score the initial score of this network. Update with sendNetworkScore. + * @param config an immutable {@link NetworkAgentConfig} for this agent. + * @param provider the {@link NetworkProvider} managing this agent. + */ + public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, + @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + this(looper, context, logTag, nc, lp, score, config, + provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(), + getLegacyNetworkInfo(config)); + } + + private static class InitialConfiguration { + public final Context context; + public final NetworkCapabilities capabilities; + public final LinkProperties properties; + public final int score; + public final NetworkAgentConfig config; + public final NetworkInfo info; + InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities, + @NonNull LinkProperties properties, int score, @NonNull NetworkAgentConfig config, + @NonNull NetworkInfo info) { + this.context = context; + this.capabilities = capabilities; + this.properties = properties; + this.score = score; + this.config = config; + this.info = info; + } + } + private volatile InitialConfiguration mInitialConfiguration; + + private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, + @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) { + mHandler = new NetworkAgentHandler(looper); + LOG_TAG = logTag; + mNetworkInfo = new NetworkInfo(ni); + this.providerId = providerId; + if (ni == null || nc == null || lp == null) { + throw new IllegalArgumentException(); + } + + mInitialConfiguration = new InitialConfiguration(context, + new NetworkCapabilities(nc, /* parcelLocationSensitiveFields */ true), + new LinkProperties(lp), score, config, ni); + } + + private class NetworkAgentHandler extends Handler { + NetworkAgentHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AGENT_CONNECTED: { + if (mRegistry != null) { + log("Received new connection while already connected!"); + } else { + if (VDBG) log("NetworkAgent fully connected"); + synchronized (mPreConnectedQueue) { + final INetworkAgentRegistry registry = (INetworkAgentRegistry) msg.obj; + mRegistry = registry; + for (RegistryAction a : mPreConnectedQueue) { + try { + a.execute(registry); + } catch (RemoteException e) { + Log.wtf(LOG_TAG, "Communication error with registry", e); + // Fall through + } + } + mPreConnectedQueue.clear(); + } + } + break; + } + case EVENT_AGENT_DISCONNECTED: { + if (DBG) log("NetworkAgent channel lost"); + // let the client know CS is done with us. + onNetworkUnwanted(); + synchronized (mPreConnectedQueue) { + mRegistry = null; + } + break; + } + case CMD_SUSPECT_BAD: { + log("Unhandled Message " + msg); + break; + } + case CMD_REQUEST_BANDWIDTH_UPDATE: { + long currentTimeMs = System.currentTimeMillis(); + if (VDBG) { + log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); + } + if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { + mBandwidthUpdateScheduled = false; + if (!mBandwidthUpdatePending.getAndSet(true)) { + onBandwidthUpdateRequested(); + } + } else { + // deliver the request at a later time rather than discard it completely. + if (!mBandwidthUpdateScheduled) { + long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS + - currentTimeMs + 1; + mBandwidthUpdateScheduled = sendEmptyMessageDelayed( + CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); + } + } + break; + } + case CMD_REPORT_NETWORK_STATUS: { + String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY); + if (VDBG) { + log("CMD_REPORT_NETWORK_STATUS(" + + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + + redirectUrl); + } + Uri uri = null; + try { + if (null != redirectUrl) { + uri = Uri.parse(redirectUrl); + } + } catch (Exception e) { + Log.wtf(LOG_TAG, "Surprising URI : " + redirectUrl, e); + } + onValidationStatus(msg.arg1 /* status */, uri); + break; + } + case CMD_SAVE_ACCEPT_UNVALIDATED: { + onSaveAcceptUnvalidated(msg.arg1 != 0); + break; + } + case CMD_START_SOCKET_KEEPALIVE: { + onStartSocketKeepalive(msg.arg1 /* slot */, + Duration.ofSeconds(msg.arg2) /* interval */, + (KeepalivePacketData) msg.obj /* packet */); + break; + } + case CMD_STOP_SOCKET_KEEPALIVE: { + onStopSocketKeepalive(msg.arg1 /* slot */); + break; + } + + case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { + onSignalStrengthThresholdsUpdated((int[]) msg.obj); + break; + } + case CMD_PREVENT_AUTOMATIC_RECONNECT: { + onAutomaticReconnectDisabled(); + break; + } + case CMD_ADD_KEEPALIVE_PACKET_FILTER: { + onAddKeepalivePacketFilter(msg.arg1 /* slot */, + (KeepalivePacketData) msg.obj /* packet */); + break; + } + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { + onRemoveKeepalivePacketFilter(msg.arg1 /* slot */); + break; + } + case CMD_REGISTER_QOS_CALLBACK: { + onQosCallbackRegistered( + msg.arg1 /* QoS callback id */, + (QosFilter) msg.obj /* QoS filter */); + break; + } + case CMD_UNREGISTER_QOS_CALLBACK: { + onQosCallbackUnregistered( + msg.arg1 /* QoS callback id */); + break; + } + } + } + } + + /** + * Register this network agent with ConnectivityService. + * + * This method can only be called once per network agent. + * + * @return the Network associated with this network agent (which can also be obtained later + * by calling getNetwork() on this agent). + * @throws IllegalStateException thrown by the system server if this network agent is + * already registered. + */ + @NonNull + public Network register() { + if (VDBG) log("Registering NetworkAgent"); + synchronized (mRegisterLock) { + if (mNetwork != null) { + throw new IllegalStateException("Agent already registered"); + } + final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context + .getSystemService(Context.CONNECTIVITY_SERVICE); + mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), + new NetworkInfo(mInitialConfiguration.info), + mInitialConfiguration.properties, mInitialConfiguration.capabilities, + mInitialConfiguration.score, mInitialConfiguration.config, providerId); + mInitialConfiguration = null; // All this memory can now be GC'd + } + return mNetwork; + } + + private static class NetworkAgentBinder extends INetworkAgent.Stub { + private static final String LOG_TAG = NetworkAgentBinder.class.getSimpleName(); + + private final Handler mHandler; + + private NetworkAgentBinder(Handler handler) { + mHandler = handler; + } + + @Override + public void onRegistered(@NonNull INetworkAgentRegistry registry) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED, registry)); + } + + @Override + public void onDisconnected() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_DISCONNECTED)); + } + + @Override + public void onBandwidthUpdateRequested() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_REQUEST_BANDWIDTH_UPDATE)); + } + + @Override + public void onValidationStatusChanged( + int validationStatus, @Nullable String captivePortalUrl) { + // TODO: consider using a parcelable as argument when the interface is structured + Bundle redirectUrlBundle = new Bundle(); + redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, captivePortalUrl); + mHandler.sendMessage(mHandler.obtainMessage(CMD_REPORT_NETWORK_STATUS, + validationStatus, 0, redirectUrlBundle)); + } + + @Override + public void onSaveAcceptUnvalidated(boolean acceptUnvalidated) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_SAVE_ACCEPT_UNVALIDATED, + acceptUnvalidated ? 1 : 0, 0)); + } + + @Override + public void onStartNattSocketKeepalive(int slot, int intervalDurationMs, + @NonNull NattKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, + slot, intervalDurationMs, packetData)); + } + + @Override + public void onStartTcpSocketKeepalive(int slot, int intervalDurationMs, + @NonNull TcpKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, + slot, intervalDurationMs, packetData)); + } + + @Override + public void onStopSocketKeepalive(int slot) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0)); + } + + @Override + public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) { + mHandler.sendMessage(mHandler.obtainMessage( + CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, thresholds)); + } + + @Override + public void onPreventAutomaticReconnect() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)); + } + + @Override + public void onAddNattKeepalivePacketFilter(int slot, + @NonNull NattKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, + slot, 0, packetData)); + } + + @Override + public void onAddTcpKeepalivePacketFilter(int slot, + @NonNull TcpKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, + slot, 0, packetData)); + } + + @Override + public void onRemoveKeepalivePacketFilter(int slot) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, + slot, 0)); + } + + @Override + public void onQosFilterCallbackRegistered(final int qosCallbackId, + final QosFilterParcelable qosFilterParcelable) { + if (qosFilterParcelable.getQosFilter() != null) { + mHandler.sendMessage( + mHandler.obtainMessage(CMD_REGISTER_QOS_CALLBACK, qosCallbackId, 0, + qosFilterParcelable.getQosFilter())); + return; + } + + Log.wtf(LOG_TAG, "onQosFilterCallbackRegistered: qos filter is null."); + } + + @Override + public void onQosCallbackUnregistered(final int qosCallbackId) { + mHandler.sendMessage(mHandler.obtainMessage( + CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null)); + } + } + + /** + * Register this network agent with a testing harness. + * + * The returned Messenger sends messages to the Handler. This allows a test to send + * this object {@code CMD_*} messages as if they came from ConnectivityService, which + * is useful for testing the behavior. + * + * @hide + */ + public INetworkAgent registerForTest(final Network network) { + log("Registering NetworkAgent for test"); + synchronized (mRegisterLock) { + mNetwork = network; + mInitialConfiguration = null; + } + return new NetworkAgentBinder(mHandler); + } + + /** + * Waits for the handler to be idle. + * This is useful for testing, and has smaller scope than an accessor to mHandler. + * TODO : move the implementation in common library with the tests + * @hide + */ + @VisibleForTesting + public boolean waitForIdle(final long timeoutMs) { + final ConditionVariable cv = new ConditionVariable(false); + mHandler.post(cv::open); + return cv.block(timeoutMs); + } + + /** + * @return The Network associated with this agent, or null if it's not registered yet. + */ + @Nullable + public Network getNetwork() { + return mNetwork; + } + + private void queueOrSendMessage(@NonNull RegistryAction action) { + synchronized (mPreConnectedQueue) { + if (mRegistry != null) { + try { + action.execute(mRegistry); + } catch (RemoteException e) { + Log.wtf(LOG_TAG, "Error executing registry action", e); + // Fall through: the channel is asynchronous and does not report errors back + } + } else { + mPreConnectedQueue.add(action); + } + } + } + + /** + * Must be called by the agent when the network's {@link LinkProperties} change. + * @param linkProperties the new LinkProperties. + */ + public final void sendLinkProperties(@NonNull LinkProperties linkProperties) { + Objects.requireNonNull(linkProperties); + final LinkProperties lp = new LinkProperties(linkProperties); + queueOrSendMessage(reg -> reg.sendLinkProperties(lp)); + } + + /** + * Must be called by the agent when the network's underlying networks change. + * + *

{@code networks} is one of the following: + *

    + *
  • a non-empty array: an array of one or more {@link Network}s, in + * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) + * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear + * first in the array.
  • + *
  • an empty array: a zero-element array, meaning that the VPN has no + * underlying network connection, and thus, app traffic will not be sent or received.
  • + *
  • null: (default) signifies that the VPN uses whatever is the system's + * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket} + * APIs mentioned above to send traffic over specific channels.
  • + *
+ * + * @param underlyingNetworks the new list of underlying networks. + * @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])} + */ + public final void setUnderlyingNetworks(@Nullable List underlyingNetworks) { + final ArrayList underlyingArray = (underlyingNetworks != null) + ? new ArrayList<>(underlyingNetworks) : null; + queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray)); + } + + /** + * Inform ConnectivityService that this agent has now connected. + * Call {@link #unregister} to disconnect. + */ + public void markConnected() { + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null /* reason */, + mNetworkInfo.getExtraInfo()); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Unregister this network agent. + * + * This signals the network has disconnected and ends its lifecycle. After this is called, + * the network is torn down and this agent can no longer be used. + */ + public void unregister() { + // When unregistering an agent nobody should use the extrainfo (or reason) any more. + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null /* reason */, + null /* extraInfo */); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Change the legacy subtype of this network agent. + * + * This is only for backward compatibility and should not be used by non-legacy network agents, + * or agents that did not use to set a subtype. As such, only TYPE_MOBILE type agents can use + * this and others will be thrown an exception if they try. + * + * @deprecated this is for backward compatibility only. + * @param legacySubtype the legacy subtype. + * @hide + */ + @Deprecated + public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) { + mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Set the ExtraInfo of this network agent. + * + * This sets the ExtraInfo field inside the NetworkInfo returned by legacy public API and the + * broadcasts about the corresponding Network. + * This is only for backward compatibility and should not be used by non-legacy network agents, + * who will be thrown an exception if they try. The extra info should only be : + *
    + *
  • For cellular agents, the APN name.
  • + *
  • For ethernet agents, the interface name.
  • + *
+ * + * @deprecated this is for backward compatibility only. + * @param extraInfo the ExtraInfo. + * @hide + */ + @Deprecated + public void setLegacyExtraInfo(@Nullable final String extraInfo) { + mNetworkInfo.setExtraInfo(extraInfo); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Must be called by the agent when it has a new NetworkInfo object. + * @hide TODO: expose something better. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public final void sendNetworkInfo(NetworkInfo networkInfo) { + queueOrSendNetworkInfo(new NetworkInfo(networkInfo)); + } + + private void queueOrSendNetworkInfo(NetworkInfo networkInfo) { + queueOrSendMessage(reg -> reg.sendNetworkInfo(networkInfo)); + } + + /** + * Must be called by the agent when the network's {@link NetworkCapabilities} change. + * @param networkCapabilities the new NetworkCapabilities. + */ + public final void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { + Objects.requireNonNull(networkCapabilities); + mBandwidthUpdatePending.set(false); + mLastBwRefreshTime = System.currentTimeMillis(); + final NetworkCapabilities nc = + new NetworkCapabilities(networkCapabilities, + /* parcelLocationSensitiveFields */ true); + queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc)); + } + + /** + * Must be called by the agent to update the score of this network. + * + * @param score the new score, between 0 and 99. + */ + public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) { + if (score < 0) { + throw new IllegalArgumentException("Score must be >= 0"); + } + queueOrSendMessage(reg -> reg.sendScore(score)); + } + + /** + * Must be called by the agent to indicate this network was manually selected by the user. + * This should be called before the NetworkInfo is marked CONNECTED so that this + * Network can be given special treatment at that time. If {@code acceptUnvalidated} is + * {@code true}, then the system will switch to this network. If it is {@code false} and the + * network cannot be validated, the system will ask the user whether to switch to this network. + * If the user confirms and selects "don't ask again", then the system will call + * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever + * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement + * {@link #saveAcceptUnvalidated} to respect the user's choice. + * @hide should move to NetworkAgentConfig. + */ + public void explicitlySelected(boolean acceptUnvalidated) { + explicitlySelected(true /* explicitlySelected */, acceptUnvalidated); + } + + /** + * Must be called by the agent to indicate whether the network was manually selected by the + * user. This should be called before the network becomes connected, so it can be given + * special treatment when it does. + * + * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true}, + * then the system will switch to this network. If {@code explicitlySelected} is {@code true} + * and {@code acceptUnvalidated} is {@code false}, and the network cannot be validated, the + * system will ask the user whether to switch to this network. If the user confirms and selects + * "don't ask again", then the system will call {@link #saveAcceptUnvalidated} to persist the + * user's choice. Thus, if the transport ever calls this method with {@code explicitlySelected} + * set to {@code true} and {@code acceptUnvalidated} set to {@code false}, it must also + * implement {@link #saveAcceptUnvalidated} to respect the user's choice. + * + * If {@code explicitlySelected} is {@code false} and {@code acceptUnvalidated} is + * {@code true}, the system will interpret this as the user having accepted partial connectivity + * on this network. Thus, the system will switch to the network and consider it validated even + * if it only provides partial connectivity, but the network is not otherwise treated specially. + * @hide should move to NetworkAgentConfig. + */ + public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) { + queueOrSendMessage(reg -> reg.sendExplicitlySelected( + explicitlySelected, acceptUnvalidated)); + } + + /** + * Called when ConnectivityService has indicated they no longer want this network. + * The parent factory should (previously) have received indication of the change + * as well, either canceling NetworkRequests or altering their score such that this + * network won't be immediately requested again. + */ + public void onNetworkUnwanted() { + unwanted(); + } + /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */ + protected void unwanted() { + } + + /** + * Called when ConnectivityService request a bandwidth update. The parent factory + * shall try to overwrite this method and produce a bandwidth update if capable. + * @hide + */ + public void onBandwidthUpdateRequested() { + pollLceData(); + } + /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */ + protected void pollLceData() { + } + + /** + * Called when the system determines the usefulness of this network. + * + * The system attempts to validate Internet connectivity on networks that provide the + * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability. + * + * Currently there are two possible values: + * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated, + * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated. + * + * This is guaranteed to be called again when the network status changes, but the system + * may also call this multiple times even if the status does not change. + * + * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}. + * @param redirectUri If Internet connectivity is being redirected (e.g., on a captive portal), + * this is the destination the probes are being redirected to, otherwise {@code null}. + */ + public void onValidationStatus(@ValidationStatus int status, @Nullable Uri redirectUri) { + networkStatus(status, null == redirectUri ? "" : redirectUri.toString()); + } + /** @hide TODO delete once subclasses have moved to onValidationStatus */ + protected void networkStatus(int status, String redirectUrl) { + } + + + /** + * Called when the user asks to remember the choice to use this network even if unvalidated. + * The transport is responsible for remembering the choice, and the next time the user connects + * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}. + * This method will only be called if {@link #explicitlySelected} was called with + * {@code acceptUnvalidated} set to {@code false}. + * @param accept whether the user wants to use the network even if unvalidated. + */ + public void onSaveAcceptUnvalidated(boolean accept) { + saveAcceptUnvalidated(accept); + } + /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */ + protected void saveAcceptUnvalidated(boolean accept) { + } + + /** + * Requests that the network hardware send the specified packet at the specified interval. + * + * @param slot the hardware slot on which to start the keepalive. + * @param interval the interval between packets, between 10 and 3600. Note that this API + * does not support sub-second precision and will round off the request. + * @param packet the packet to send. + */ + // seconds is from SocketKeepalive.MIN_INTERVAL_SEC to MAX_INTERVAL_SEC, but these should + // not be exposed as constants because they may change in the future (API guideline 4.8) + // and should have getters if exposed at all. Getters can't be used in the annotation, + // so the values unfortunately need to be copied. + public void onStartSocketKeepalive(int slot, @NonNull Duration interval, + @NonNull KeepalivePacketData packet) { + final long intervalSeconds = interval.getSeconds(); + if (intervalSeconds < SocketKeepalive.MIN_INTERVAL_SEC + || intervalSeconds > SocketKeepalive.MAX_INTERVAL_SEC) { + throw new IllegalArgumentException("Interval needs to be comprised between " + + SocketKeepalive.MIN_INTERVAL_SEC + " and " + SocketKeepalive.MAX_INTERVAL_SEC + + " but was " + intervalSeconds); + } + final Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, + (int) intervalSeconds, packet); + startSocketKeepalive(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */ + protected void startSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); + } + + /** + * Requests that the network hardware stop a previously-started keepalive. + * + * @param slot the hardware slot on which to stop the keepalive. + */ + public void onStopSocketKeepalive(int slot) { + Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null); + stopSocketKeepalive(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */ + protected void stopSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); + } + + /** + * Must be called by the agent when a socket keepalive event occurs. + * + * @param slot the hardware slot on which the event occurred. + * @param event the event that occurred, as one of the SocketKeepalive.ERROR_* + * or SocketKeepalive.SUCCESS constants. + */ + public final void sendSocketKeepaliveEvent(int slot, + @SocketKeepalive.KeepaliveEvent int event) { + queueOrSendMessage(reg -> reg.sendSocketKeepaliveEvent(slot, event)); + } + /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */ + public void onSocketKeepaliveEvent(int slot, int reason) { + sendSocketKeepaliveEvent(slot, reason); + } + + /** + * Called by ConnectivityService to add specific packet filter to network hardware to block + * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support + * this feature must override this method. + * + * @param slot the hardware slot on which the keepalive should be sent. + * @param packet the packet that is being sent. + */ + public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) { + Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet); + addKeepalivePacketFilter(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */ + protected void addKeepalivePacketFilter(Message msg) { + } + + /** + * Called by ConnectivityService to remove a packet filter installed with + * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature + * must override this method. + * + * @param slot the hardware slot on which the keepalive is being sent. + */ + public void onRemoveKeepalivePacketFilter(int slot) { + Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null); + removeKeepalivePacketFilter(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */ + protected void removeKeepalivePacketFilter(Message msg) { + } + + /** + * Called by ConnectivityService to inform this network agent of signal strength thresholds + * that when crossed should trigger a system wakeup and a NetworkCapabilities update. + * + * When the system updates the list of thresholds that should wake up the CPU for a + * given agent it will call this method on the agent. The agent that implement this + * should implement it in hardware so as to ensure the CPU will be woken up on breach. + * Agents are expected to react to a breach by sending an updated NetworkCapabilities + * object with the appropriate signal strength to sendNetworkCapabilities. + * + * The specific units are bearer-dependent. See details on the units and requests in + * {@link NetworkCapabilities.Builder#setSignalStrength}. + * + * @param thresholds the array of thresholds that should trigger wakeups. + */ + public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) { + setSignalStrengthThresholds(thresholds); + } + /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */ + protected void setSignalStrengthThresholds(int[] thresholds) { + } + + /** + * Called when the user asks to not stay connected to this network because it was found to not + * provide Internet access. Usually followed by call to {@code unwanted}. The transport is + * responsible for making sure the device does not automatically reconnect to the same network + * after the {@code unwanted} call. + */ + public void onAutomaticReconnectDisabled() { + preventAutomaticReconnect(); + } + /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */ + protected void preventAutomaticReconnect() { + } + + /** + * Called when a qos callback is registered with a filter. + * @param qosCallbackId the id for the callback registered + * @param filter the filter being registered + */ + public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) { + } + + /** + * Called when a qos callback is registered with a filter. + *

+ * Any QoS events that are sent with the same callback id after this method is called + * are a no-op. + * + * @param qosCallbackId the id for the callback being unregistered + */ + public void onQosCallbackUnregistered(final int qosCallbackId) { + } + + + /** + * Sends the attributes of Eps Bearer Qos Session back to the Application + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + * @param attributes the attributes of the Eps Qos Session + */ + public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId, + @NonNull final EpsBearerQosSessionAttributes attributes) { + Objects.requireNonNull(attributes, "The attributes must be non-null"); + queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), + attributes)); + } + + /** + * Sends event that the Eps Qos Session was lost. + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + */ + public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) { + queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER))); + } + + /** + * Sends the exception type back to the application. + * + * The NetworkAgent should not send anymore messages with this id. + * + * @param qosCallbackId the callback id this exception belongs to + * @param exceptionType the type of exception + */ + public final void sendQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType)); + } + + + /** @hide */ + protected void log(final String s) { + Log.d(LOG_TAG, "NetworkAgent: " + s); + } +} diff --git a/framework/src/android/net/NetworkAgentConfig.aidl b/framework/src/android/net/NetworkAgentConfig.aidl new file mode 100644 index 0000000000..cb70bdd312 --- /dev/null +++ b/framework/src/android/net/NetworkAgentConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 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; + +parcelable NetworkAgentConfig; diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java new file mode 100644 index 0000000000..664c2650ff --- /dev/null +++ b/framework/src/android/net/NetworkAgentConfig.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2014 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 android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Allows a network transport to provide the system with policy and configuration information about + * a particular network when registering a {@link NetworkAgent}. This information cannot change once the agent is registered. + * + * @hide + */ +@SystemApi +public final class NetworkAgentConfig implements Parcelable { + + /** + * If the {@link Network} is a VPN, whether apps are allowed to bypass the + * VPN. This is set by a {@link VpnService} and used by + * {@link ConnectivityManager} when creating a VPN. + * + * @hide + */ + public boolean allowBypass; + + /** + * Set if the network was manually/explicitly connected to by the user either from settings + * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi + * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to + * connect to a particular access point is also explicit, though this may change in the future + * as we want apps to use the multinetwork apis. + * + * @hide + */ + public boolean explicitlySelected; + + /** + * @return whether this network was explicitly selected by the user. + */ + public boolean isExplicitlySelected() { + return explicitlySelected; + } + + /** + * Set if the user desires to use this network even if it is unvalidated. This field has meaning + * only if {@link explicitlySelected} is true. If it is, this field must also be set to the + * appropriate value based on previous user choice. + * + * TODO : rename this field to match its accessor + * @hide + */ + public boolean acceptUnvalidated; + + /** + * @return whether the system should accept this network even if it doesn't validate. + */ + public boolean isUnvalidatedConnectivityAcceptable() { + return acceptUnvalidated; + } + + /** + * Whether the user explicitly set that this network should be validated even if presence of + * only partial internet connectivity. + * + * TODO : rename this field to match its accessor + * @hide + */ + public boolean acceptPartialConnectivity; + + /** + * @return whether the system should validate this network even if it only offers partial + * Internet connectivity. + */ + public boolean isPartialConnectivityAcceptable() { + return acceptPartialConnectivity; + } + + /** + * Set to avoid surfacing the "Sign in to network" notification. + * if carrier receivers/apps are registered to handle the carrier-specific provisioning + * procedure, a carrier specific provisioning notification will be placed. + * only one notification should be displayed. This field is set based on + * which notification should be used for provisioning. + * + * @hide + */ + public boolean provisioningNotificationDisabled; + + /** + * + * @return whether the sign in to network notification is enabled by this configuration. + * @hide + */ + public boolean isProvisioningNotificationEnabled() { + return !provisioningNotificationDisabled; + } + + /** + * For mobile networks, this is the subscriber ID (such as IMSI). + * + * @hide + */ + public String subscriberId; + + /** + * @return the subscriber ID, or null if none. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @Nullable + public String getSubscriberId() { + return subscriberId; + } + + /** + * Set to skip 464xlat. This means the device will treat the network as IPv6-only and + * will not attempt to detect a NAT64 via RFC 7050 DNS lookups. + * + * @hide + */ + public boolean skip464xlat; + + /** + * @return whether NAT64 prefix detection is enabled. + * @hide + */ + public boolean isNat64DetectionEnabled() { + return !skip464xlat; + } + + /** + * The legacy type of this network agent, or TYPE_NONE if unset. + * @hide + */ + public int legacyType = ConnectivityManager.TYPE_NONE; + + /** + * @return the legacy type + */ + @ConnectivityManager.LegacyNetworkType + public int getLegacyType() { + return legacyType; + } + + /** + * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. + * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. + * + * This is not parceled, because it would not make sense. + * + * @hide + */ + public transient boolean hasShownBroken; + + /** + * The name of the legacy network type. It's a free-form string used in logging. + * @hide + */ + @NonNull + public String legacyTypeName = ""; + + /** + * @return the name of the legacy network type. It's a free-form string used in logging. + */ + @NonNull + public String getLegacyTypeName() { + return legacyTypeName; + } + + /** + * The legacy extra info of the agent. The extra info should only be : + *

    + *
  • For cellular agents, the APN name.
  • + *
  • For ethernet agents, the interface name.
  • + *
+ * @hide + */ + @NonNull + private String mLegacyExtraInfo = ""; + + /** + * The legacy extra info of the agent. + * @hide + */ + @NonNull + public String getLegacyExtraInfo() { + return mLegacyExtraInfo; + } + + /** @hide */ + public NetworkAgentConfig() { + } + + /** @hide */ + public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) { + if (nac != null) { + allowBypass = nac.allowBypass; + explicitlySelected = nac.explicitlySelected; + acceptUnvalidated = nac.acceptUnvalidated; + acceptPartialConnectivity = nac.acceptPartialConnectivity; + subscriberId = nac.subscriberId; + provisioningNotificationDisabled = nac.provisioningNotificationDisabled; + skip464xlat = nac.skip464xlat; + legacyType = nac.legacyType; + legacyTypeName = nac.legacyTypeName; + mLegacyExtraInfo = nac.mLegacyExtraInfo; + } + } + + /** + * Builder class to facilitate constructing {@link NetworkAgentConfig} objects. + */ + public static final class Builder { + private final NetworkAgentConfig mConfig = new NetworkAgentConfig(); + + /** + * Sets whether the network was explicitly selected by the user. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setExplicitlySelected(final boolean explicitlySelected) { + mConfig.explicitlySelected = explicitlySelected; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found not to offer + * Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setUnvalidatedConnectivityAcceptable( + final boolean unvalidatedConnectivityAcceptable) { + mConfig.acceptUnvalidated = unvalidatedConnectivityAcceptable; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found to only offer + * partial Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setPartialConnectivityAcceptable( + final boolean partialConnectivityAcceptable) { + mConfig.acceptPartialConnectivity = partialConnectivityAcceptable; + return this; + } + + /** + * Sets the subscriber ID for this network. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + public Builder setSubscriberId(@Nullable String subscriberId) { + mConfig.subscriberId = subscriberId; + return this; + } + + /** + * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power + * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder disableNat64Detection() { + mConfig.skip464xlat = true; + return this; + } + + /** + * Disables the "Sign in to network" notification. Used if the network transport will + * perform its own carrier-specific provisioning procedure. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder disableProvisioningNotification() { + mConfig.provisioningNotificationDisabled = true; + return this; + } + + /** + * Sets the legacy type for this network. + * + * @param legacyType the type + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setLegacyType(int legacyType) { + mConfig.legacyType = legacyType; + return this; + } + + /** + * Sets the name of the legacy type of the agent. It's a free-form string used in logging. + * @param legacyTypeName the name + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setLegacyTypeName(@NonNull String legacyTypeName) { + mConfig.legacyTypeName = legacyTypeName; + return this; + } + + /** + * Sets the legacy extra info of the agent. + * @param legacyExtraInfo the legacy extra info. + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) { + mConfig.mLegacyExtraInfo = legacyExtraInfo; + return this; + } + + /** + * Returns the constructed {@link NetworkAgentConfig} object. + */ + @NonNull + public NetworkAgentConfig build() { + return mConfig; + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final NetworkAgentConfig that = (NetworkAgentConfig) o; + return allowBypass == that.allowBypass + && explicitlySelected == that.explicitlySelected + && acceptUnvalidated == that.acceptUnvalidated + && acceptPartialConnectivity == that.acceptPartialConnectivity + && provisioningNotificationDisabled == that.provisioningNotificationDisabled + && skip464xlat == that.skip464xlat + && legacyType == that.legacyType + && Objects.equals(subscriberId, that.subscriberId) + && Objects.equals(legacyTypeName, that.legacyTypeName) + && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo); + } + + @Override + public int hashCode() { + return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated, + acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId, + skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo); + } + + @Override + public String toString() { + return "NetworkAgentConfig {" + + " allowBypass = " + allowBypass + + ", explicitlySelected = " + explicitlySelected + + ", acceptUnvalidated = " + acceptUnvalidated + + ", acceptPartialConnectivity = " + acceptPartialConnectivity + + ", provisioningNotificationDisabled = " + provisioningNotificationDisabled + + ", subscriberId = '" + subscriberId + '\'' + + ", skip464xlat = " + skip464xlat + + ", legacyType = " + legacyType + + ", hasShownBroken = " + hasShownBroken + + ", legacyTypeName = '" + legacyTypeName + '\'' + + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\'' + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(allowBypass ? 1 : 0); + out.writeInt(explicitlySelected ? 1 : 0); + out.writeInt(acceptUnvalidated ? 1 : 0); + out.writeInt(acceptPartialConnectivity ? 1 : 0); + out.writeString(subscriberId); + out.writeInt(provisioningNotificationDisabled ? 1 : 0); + out.writeInt(skip464xlat ? 1 : 0); + out.writeInt(legacyType); + out.writeString(legacyTypeName); + out.writeString(mLegacyExtraInfo); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public NetworkAgentConfig createFromParcel(Parcel in) { + NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); + networkAgentConfig.allowBypass = in.readInt() != 0; + networkAgentConfig.explicitlySelected = in.readInt() != 0; + networkAgentConfig.acceptUnvalidated = in.readInt() != 0; + networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0; + networkAgentConfig.subscriberId = in.readString(); + networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0; + networkAgentConfig.skip464xlat = in.readInt() != 0; + networkAgentConfig.legacyType = in.readInt(); + networkAgentConfig.legacyTypeName = in.readString(); + networkAgentConfig.mLegacyExtraInfo = in.readString(); + return networkAgentConfig; + } + + @Override + public NetworkAgentConfig[] newArray(int size) { + return new NetworkAgentConfig[size]; + } + }; +} diff --git a/framework/src/android/net/NetworkCapabilities.aidl b/framework/src/android/net/NetworkCapabilities.aidl new file mode 100644 index 0000000000..01d328605d --- /dev/null +++ b/framework/src/android/net/NetworkCapabilities.aidl @@ -0,0 +1,21 @@ +/* +** +** Copyright (C) 2014 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; + +@JavaOnlyStableParcelable parcelable NetworkCapabilities; + diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java new file mode 100644 index 0000000000..3843b9ab93 --- /dev/null +++ b/framework/src/android/net/NetworkCapabilities.java @@ -0,0 +1,2517 @@ +/* + * Copyright (C) 2014 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 com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.net.ConnectivityManager.NetworkCallback; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.BitUtils; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; + +/** + * Representation of the capabilities of an active network. Instances are + * typically obtained through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} + * or {@link ConnectivityManager#getNetworkCapabilities(Network)}. + *

+ * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of + * network selection. Rather than indicate a need for Wi-Fi because an + * application needs high bandwidth and risk obsolescence when a new, fast + * network appears (like LTE), the application should specify it needs high + * bandwidth. Similarly if an application needs an unmetered network for a bulk + * transfer it can specify that rather than assuming all cellular based + * connections are metered and all Wi-Fi based connections are not. + */ +public final class NetworkCapabilities implements Parcelable { + private static final String TAG = "NetworkCapabilities"; + + // Set to true when private DNS is broken. + private boolean mPrivateDnsBroken; + + /** + * Uid of the app making the request. + */ + private int mRequestorUid; + + /** + * Package name of the app making the request. + */ + private String mRequestorPackageName; + + /** + * Indicates whether parceling should preserve fields that are set based on permissions of + * the process receiving the {@link NetworkCapabilities}. + */ + private final boolean mParcelLocationSensitiveFields; + + public NetworkCapabilities() { + mParcelLocationSensitiveFields = false; + clearAll(); + mNetworkCapabilities = DEFAULT_CAPABILITIES; + } + + public NetworkCapabilities(NetworkCapabilities nc) { + this(nc, false /* parcelLocationSensitiveFields */); + } + + /** + * Make a copy of NetworkCapabilities. + * + * @param nc Original NetworkCapabilities + * @param parcelLocationSensitiveFields Whether to parcel location sensitive data or not. + * @hide + */ + @SystemApi + public NetworkCapabilities( + @Nullable NetworkCapabilities nc, boolean parcelLocationSensitiveFields) { + mParcelLocationSensitiveFields = parcelLocationSensitiveFields; + if (nc != null) { + set(nc); + } + } + + /** + * Completely clears the contents of this object, removing even the capabilities that are set + * by default when the object is constructed. + * @hide + */ + public void clearAll() { + // Ensures that the internal copies maintained by the connectivity stack does not set + // this bit. + if (mParcelLocationSensitiveFields) { + throw new UnsupportedOperationException( + "Cannot clear NetworkCapabilities when parcelLocationSensitiveFields is set"); + } + mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0; + mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + mNetworkSpecifier = null; + mTransportInfo = null; + mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; + mUids = null; + mAdministratorUids = new int[0]; + mOwnerUid = Process.INVALID_UID; + mSSID = null; + mPrivateDnsBroken = false; + mRequestorUid = Process.INVALID_UID; + mRequestorPackageName = null; + } + + /** + * Set all contents of this object to the contents of a NetworkCapabilities. + * + * @param nc Original NetworkCapabilities + * @hide + */ + public void set(@NonNull NetworkCapabilities nc) { + mNetworkCapabilities = nc.mNetworkCapabilities; + mTransportTypes = nc.mTransportTypes; + mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps; + mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; + mNetworkSpecifier = nc.mNetworkSpecifier; + if (nc.getTransportInfo() != null) { + setTransportInfo(nc.getTransportInfo().makeCopy(mParcelLocationSensitiveFields)); + } else { + setTransportInfo(null); + } + mSignalStrength = nc.mSignalStrength; + setUids(nc.mUids); // Will make the defensive copy + setAdministratorUids(nc.getAdministratorUids()); + mOwnerUid = nc.mOwnerUid; + mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; + mSSID = nc.mSSID; + mPrivateDnsBroken = nc.mPrivateDnsBroken; + mRequestorUid = nc.mRequestorUid; + mRequestorPackageName = nc.mRequestorPackageName; + } + + /** + * Represents the network's capabilities. If any are specified they will be satisfied + * by any Network that matches all of them. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private long mNetworkCapabilities; + + /** + * If any capabilities specified here they must not exist in the matching Network. + */ + private long mUnwantedNetworkCapabilities; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "NET_CAPABILITY_" }, value = { + NET_CAPABILITY_MMS, + NET_CAPABILITY_SUPL, + NET_CAPABILITY_DUN, + NET_CAPABILITY_FOTA, + NET_CAPABILITY_IMS, + NET_CAPABILITY_CBS, + NET_CAPABILITY_WIFI_P2P, + NET_CAPABILITY_IA, + NET_CAPABILITY_RCS, + NET_CAPABILITY_XCAP, + NET_CAPABILITY_EIMS, + NET_CAPABILITY_NOT_METERED, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_VALIDATED, + NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_NOT_ROAMING, + NET_CAPABILITY_FOREGROUND, + NET_CAPABILITY_NOT_CONGESTED, + NET_CAPABILITY_NOT_SUSPENDED, + NET_CAPABILITY_OEM_PAID, + NET_CAPABILITY_MCX, + NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NET_CAPABILITY_OEM_PRIVATE, + NET_CAPABILITY_VEHICLE_INTERNAL, + NET_CAPABILITY_NOT_VCN_MANAGED, + }) + public @interface NetCapability { } + + /** + * Indicates this is a network that has the ability to reach the + * carrier's MMSC for sending and receiving MMS messages. + */ + public static final int NET_CAPABILITY_MMS = 0; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * SUPL server, used to retrieve GPS information. + */ + public static final int NET_CAPABILITY_SUPL = 1; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * DUN or tethering gateway. + */ + public static final int NET_CAPABILITY_DUN = 2; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * FOTA portal, used for over the air updates. + */ + public static final int NET_CAPABILITY_FOTA = 3; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * IMS servers, used for network registration and signaling. + */ + public static final int NET_CAPABILITY_IMS = 4; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * CBS servers, used for carrier specific services. + */ + public static final int NET_CAPABILITY_CBS = 5; + + /** + * Indicates this is a network that has the ability to reach a Wi-Fi direct + * peer. + */ + public static final int NET_CAPABILITY_WIFI_P2P = 6; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * Initial Attach servers. + */ + public static final int NET_CAPABILITY_IA = 7; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * RCS servers, used for Rich Communication Services. + */ + public static final int NET_CAPABILITY_RCS = 8; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * XCAP servers, used for configuration and control. + */ + public static final int NET_CAPABILITY_XCAP = 9; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * Emergency IMS servers or other services, used for network signaling + * during emergency calls. + */ + public static final int NET_CAPABILITY_EIMS = 10; + + /** + * Indicates that this network is unmetered. + */ + public static final int NET_CAPABILITY_NOT_METERED = 11; + + /** + * Indicates that this network should be able to reach the internet. + */ + public static final int NET_CAPABILITY_INTERNET = 12; + + /** + * Indicates that this network is available for general use. If this is not set + * applications should not attempt to communicate on this network. Note that this + * is simply informative and not enforcement - enforcement is handled via other means. + * Set by default. + */ + public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; + + /** + * Indicates that the user has indicated implicit trust of this network. This + * generally means it's a sim-selected carrier, a plugged in ethernet, a paired + * BT device or a wifi the user asked to connect to. Untrusted networks + * are probably limited to unknown wifi AP. Set by default. + */ + public static final int NET_CAPABILITY_TRUSTED = 14; + + /** + * Indicates that this network is not a VPN. This capability is set by default and should be + * explicitly cleared for VPN networks. + */ + public static final int NET_CAPABILITY_NOT_VPN = 15; + + /** + * Indicates that connectivity on this network was successfully validated. For example, for a + * network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully + * detected. + */ + public static final int NET_CAPABILITY_VALIDATED = 16; + + /** + * Indicates that this network was found to have a captive portal in place last time it was + * probed. + */ + public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; + + /** + * Indicates that this network is not roaming. + */ + public static final int NET_CAPABILITY_NOT_ROAMING = 18; + + /** + * Indicates that this network is available for use by apps, and not a network that is being + * kept up in the background to facilitate fast network switching. + */ + public static final int NET_CAPABILITY_FOREGROUND = 19; + + /** + * Indicates that this network is not congested. + *

+ * When a network is congested, applications should defer network traffic + * that can be done at a later time, such as uploading analytics. + */ + public static final int NET_CAPABILITY_NOT_CONGESTED = 20; + + /** + * Indicates that this network is not currently suspended. + *

+ * When a network is suspended, the network's IP addresses and any connections + * established on the network remain valid, but the network is temporarily unable + * to transfer data. This can happen, for example, if a cellular network experiences + * a temporary loss of signal, such as when driving through a tunnel, etc. + * A network with this capability is not suspended, so is expected to be able to + * transfer data. + */ + public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; + + /** + * Indicates that traffic that goes through this network is paid by oem. For example, + * this network can be used by system apps to upload telemetry data. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_OEM_PAID = 22; + + /** + * Indicates this is a network that has the ability to reach a carrier's Mission Critical + * servers. + */ + public static final int NET_CAPABILITY_MCX = 23; + + /** + * Indicates that this network was tested to only provide partial connectivity. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; + + /** + * Indicates that this network is temporarily unmetered. + *

+ * This capability will be set for networks that are generally metered, but are currently + * unmetered, e.g., because the user is in a particular area. This capability can be changed at + * any time. When it is removed, applications are responsible for stopping any data transfer + * that should not occur on a metered network. + * Note that most apps should use {@link #NET_CAPABILITY_NOT_METERED} instead. For more + * information, see https://developer.android.com/about/versions/11/features/5g#meteredness. + */ + public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; + + /** + * Indicates that this network is private to the OEM and meant only for OEM use. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_OEM_PRIVATE = 26; + + /** + * Indicates this is an internal vehicle network, meant to communicate with other + * automotive systems. + * + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; + + /** + * Indicates that this network is not managed by a Virtual Carrier Network (VCN). + * + * TODO(b/177299683): Add additional clarifying javadoc. + * @hide + */ + public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED; + + /** + * Network capabilities that are expected to be mutable, i.e., can change while a particular + * network is connected. + */ + private static final long MUTABLE_CAPABILITIES = + // TRUSTED can change when user explicitly connects to an untrusted network in Settings. + // http://b/18206275 + (1 << NET_CAPABILITY_TRUSTED) + | (1 << NET_CAPABILITY_VALIDATED) + | (1 << NET_CAPABILITY_CAPTIVE_PORTAL) + | (1 << NET_CAPABILITY_NOT_ROAMING) + | (1 << NET_CAPABILITY_FOREGROUND) + | (1 << NET_CAPABILITY_NOT_CONGESTED) + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY) + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); + + /** + * Network capabilities that are not allowed in NetworkRequests. This exists because the + * NetworkFactory / NetworkAgent model does not deal well with the situation where a + * capability's presence cannot be known in advance. If such a capability is requested, then we + * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then + * get immediately torn down because they do not have the requested capability. + */ + // Note that as a historical exception, the TRUSTED and NOT_VCN_MANAGED capabilities + // are mutable but requestable. Factories are responsible for not getting + // in an infinite loop about these. + private static final long NON_REQUESTABLE_CAPABILITIES = + MUTABLE_CAPABILITIES + & ~(1 << NET_CAPABILITY_TRUSTED) + & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED); + + /** + * Capabilities that are set by default when the object is constructed. + */ + private static final long DEFAULT_CAPABILITIES = + (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_TRUSTED) + | (1 << NET_CAPABILITY_NOT_VPN); + + /** + * Capabilities that suggest that a network is restricted. + * {@see #maybeMarkCapabilitiesRestricted}, {@see #FORCE_RESTRICTED_CAPABILITIES} + */ + @VisibleForTesting + /* package */ static final long RESTRICTED_CAPABILITIES = + (1 << NET_CAPABILITY_CBS) + | (1 << NET_CAPABILITY_DUN) + | (1 << NET_CAPABILITY_EIMS) + | (1 << NET_CAPABILITY_FOTA) + | (1 << NET_CAPABILITY_IA) + | (1 << NET_CAPABILITY_IMS) + | (1 << NET_CAPABILITY_MCX) + | (1 << NET_CAPABILITY_RCS) + | (1 << NET_CAPABILITY_VEHICLE_INTERNAL) + | (1 << NET_CAPABILITY_XCAP); + + /** + * Capabilities that force network to be restricted. + * {@see #maybeMarkCapabilitiesRestricted}. + */ + private static final long FORCE_RESTRICTED_CAPABILITIES = + (1 << NET_CAPABILITY_OEM_PAID) + | (1 << NET_CAPABILITY_OEM_PRIVATE); + + /** + * Capabilities that suggest that a network is unrestricted. + * {@see #maybeMarkCapabilitiesRestricted}. + */ + @VisibleForTesting + /* package */ static final long UNRESTRICTED_CAPABILITIES = + (1 << NET_CAPABILITY_INTERNET) + | (1 << NET_CAPABILITY_MMS) + | (1 << NET_CAPABILITY_SUPL) + | (1 << NET_CAPABILITY_WIFI_P2P); + + /** + * Capabilities that are managed by ConnectivityService. + */ + private static final long CONNECTIVITY_MANAGED_CAPABILITIES = + (1 << NET_CAPABILITY_VALIDATED) + | (1 << NET_CAPABILITY_CAPTIVE_PORTAL) + | (1 << NET_CAPABILITY_FOREGROUND) + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY); + + /** + * Capabilities that are allowed for test networks. This list must be set so that it is safe + * for an unprivileged user to create a network with these capabilities via shell. As such, + * it must never contain capabilities that are generally useful to the system, such as + * INTERNET, IMS, SUPL, etc. + */ + private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES = + (1 << NET_CAPABILITY_NOT_METERED) + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_NOT_VPN) + | (1 << NET_CAPABILITY_NOT_ROAMING) + | (1 << NET_CAPABILITY_NOT_CONGESTED) + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); + + /** + * Adds the given capability to this {@code NetworkCapability} instance. + * Note that when searching for a network to satisfy a request, all capabilities + * requested must be satisfied. + * + * @param capability the capability to be added. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities addCapability(@NetCapability int capability) { + // If the given capability was previously added to the list of unwanted capabilities + // then the capability will also be removed from the list of unwanted capabilities. + // TODO: Consider adding unwanted capabilities to the public API and mention this + // in the documentation. + checkValidCapability(capability); + mNetworkCapabilities |= 1 << capability; + mUnwantedNetworkCapabilities &= ~(1 << capability); // remove from unwanted capability list + return this; + } + + /** + * Adds the given capability to the list of unwanted capabilities of this + * {@code NetworkCapability} instance. Note that when searching for a network to + * satisfy a request, the network must not contain any capability from unwanted capability + * list. + *

+ * If the capability was previously added to the list of required capabilities (for + * example, it was there by default or added using {@link #addCapability(int)} method), then + * it will be removed from the list of required capabilities as well. + * + * @see #addCapability(int) + * @hide + */ + public void addUnwantedCapability(@NetCapability int capability) { + checkValidCapability(capability); + mUnwantedNetworkCapabilities |= 1 << capability; + mNetworkCapabilities &= ~(1 << capability); // remove from requested capabilities + } + + /** + * Removes (if found) the given capability from this {@code NetworkCapability} instance. + * + * @param capability the capability to be removed. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) { + // Note that this method removes capabilities that were added via addCapability(int), + // addUnwantedCapability(int) or setCapabilities(int[], int[]). + checkValidCapability(capability); + final long mask = ~(1 << capability); + mNetworkCapabilities &= mask; + mUnwantedNetworkCapabilities &= mask; + return this; + } + + /** + * Sets (or clears) the given capability on this {@link NetworkCapabilities} + * instance. + * @hide + */ + public @NonNull NetworkCapabilities setCapability(@NetCapability int capability, + boolean value) { + if (value) { + addCapability(capability); + } else { + removeCapability(capability); + } + return this; + } + + /** + * Gets all the capabilities set on this {@code NetworkCapability} instance. + * + * @return an array of capability values for this instance. + * @hide + */ + @UnsupportedAppUsage + public @NetCapability int[] getCapabilities() { + return BitUtils.unpackBits(mNetworkCapabilities); + } + + /** + * Gets all the unwanted capabilities set on this {@code NetworkCapability} instance. + * + * @return an array of unwanted capability values for this instance. + * @hide + */ + public @NetCapability int[] getUnwantedCapabilities() { + return BitUtils.unpackBits(mUnwantedNetworkCapabilities); + } + + + /** + * Sets all the capabilities set on this {@code NetworkCapability} instance. + * This overwrites any existing capabilities. + * + * @hide + */ + public void setCapabilities(@NetCapability int[] capabilities, + @NetCapability int[] unwantedCapabilities) { + mNetworkCapabilities = BitUtils.packBits(capabilities); + mUnwantedNetworkCapabilities = BitUtils.packBits(unwantedCapabilities); + } + + /** + * @deprecated use {@link #setCapabilities(int[], int[])} + * @hide + */ + @Deprecated + public void setCapabilities(@NetCapability int[] capabilities) { + setCapabilities(capabilities, new int[] {}); + } + + /** + * Tests for the presence of a capability on this instance. + * + * @param capability the capabilities to be tested for. + * @return {@code true} if set on this instance. + */ + public boolean hasCapability(@NetCapability int capability) { + return isValidCapability(capability) + && ((mNetworkCapabilities & (1 << capability)) != 0); + } + + /** @hide */ + public boolean hasUnwantedCapability(@NetCapability int capability) { + return isValidCapability(capability) + && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0); + } + + /** + * Check if this NetworkCapabilities has system managed capabilities or not. + * @hide + */ + public boolean hasConnectivityManagedCapability() { + return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0); + } + + /** Note this method may result in having the same capability in wanted and unwanted lists. */ + private void combineNetCapabilities(@NonNull NetworkCapabilities nc) { + this.mNetworkCapabilities |= nc.mNetworkCapabilities; + this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities; + } + + /** + * Convenience function that returns a human-readable description of the first mutable + * capability we find. Used to present an error message to apps that request mutable + * capabilities. + * + * @hide + */ + public @Nullable String describeFirstNonRequestableCapability() { + final long nonRequestable = (mNetworkCapabilities | mUnwantedNetworkCapabilities) + & NON_REQUESTABLE_CAPABILITIES; + + if (nonRequestable != 0) { + return capabilityNameOf(BitUtils.unpackBits(nonRequestable)[0]); + } + if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth"; + if (hasSignalStrength()) return "signalStrength"; + if (isPrivateDnsBroken()) { + return "privateDnsBroken"; + } + return null; + } + + private boolean satisfiedByNetCapabilities(@NonNull NetworkCapabilities nc, + boolean onlyImmutable) { + long requestedCapabilities = mNetworkCapabilities; + long requestedUnwantedCapabilities = mUnwantedNetworkCapabilities; + long providedCapabilities = nc.mNetworkCapabilities; + + if (onlyImmutable) { + requestedCapabilities &= ~MUTABLE_CAPABILITIES; + requestedUnwantedCapabilities &= ~MUTABLE_CAPABILITIES; + } + return ((providedCapabilities & requestedCapabilities) == requestedCapabilities) + && ((requestedUnwantedCapabilities & providedCapabilities) == 0); + } + + /** @hide */ + public boolean equalsNetCapabilities(@NonNull NetworkCapabilities nc) { + return (nc.mNetworkCapabilities == this.mNetworkCapabilities) + && (nc.mUnwantedNetworkCapabilities == this.mUnwantedNetworkCapabilities); + } + + private boolean equalsNetCapabilitiesRequestable(@NonNull NetworkCapabilities that) { + return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == + (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)) + && ((this.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == + (that.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)); + } + + /** + * Deduces that all the capabilities it provides are typically provided by restricted networks + * or not. + * + * @return {@code true} if the network should be restricted. + * @hide + */ + public boolean deduceRestrictedCapability() { + // Check if we have any capability that forces the network to be restricted. + final boolean forceRestrictedCapability = + (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0; + + // Verify there aren't any unrestricted capabilities. If there are we say + // the whole thing is unrestricted unless it is forced to be restricted. + final boolean hasUnrestrictedCapabilities = + (mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0; + + // Must have at least some restricted capabilities. + final boolean hasRestrictedCapabilities = + (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0; + + return forceRestrictedCapability + || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities); + } + + /** + * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted. + * + * @hide + */ + public void maybeMarkCapabilitiesRestricted() { + if (deduceRestrictedCapability()) { + removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + } + } + + /** + * Test networks have strong restrictions on what capabilities they can have. Enforce these + * restrictions. + * @hide + */ + public void restrictCapabilitesForTestNetwork(int creatorUid) { + final long originalCapabilities = mNetworkCapabilities; + final long originalTransportTypes = mTransportTypes; + final NetworkSpecifier originalSpecifier = mNetworkSpecifier; + final int originalSignalStrength = mSignalStrength; + final int originalOwnerUid = getOwnerUid(); + final int[] originalAdministratorUids = getAdministratorUids(); + clearAll(); + mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS) + | (1 << TRANSPORT_TEST); + mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES; + mNetworkSpecifier = originalSpecifier; + mSignalStrength = originalSignalStrength; + + // Only retain the owner and administrator UIDs if they match the app registering the remote + // caller that registered the network. + if (originalOwnerUid == creatorUid) { + setOwnerUid(creatorUid); + } + if (ArrayUtils.contains(originalAdministratorUids, creatorUid)) { + setAdministratorUids(new int[] {creatorUid}); + } + // There is no need to clear the UIDs, they have already been cleared by clearAll() above. + } + + /** + * Representing the transport type. Apps should generally not care about transport. A + * request for a fast internet connection could be satisfied by a number of different + * transports. If any are specified here it will be satisfied a Network that matches + * any of them. If a caller doesn't care about the transport it should not specify any. + */ + private long mTransportTypes; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TRANSPORT_" }, value = { + TRANSPORT_CELLULAR, + TRANSPORT_WIFI, + TRANSPORT_BLUETOOTH, + TRANSPORT_ETHERNET, + TRANSPORT_VPN, + TRANSPORT_WIFI_AWARE, + TRANSPORT_LOWPAN, + TRANSPORT_TEST, + }) + public @interface Transport { } + + /** + * Indicates this network uses a Cellular transport. + */ + public static final int TRANSPORT_CELLULAR = 0; + + /** + * Indicates this network uses a Wi-Fi transport. + */ + public static final int TRANSPORT_WIFI = 1; + + /** + * Indicates this network uses a Bluetooth transport. + */ + public static final int TRANSPORT_BLUETOOTH = 2; + + /** + * Indicates this network uses an Ethernet transport. + */ + public static final int TRANSPORT_ETHERNET = 3; + + /** + * Indicates this network uses a VPN transport. + */ + public static final int TRANSPORT_VPN = 4; + + /** + * Indicates this network uses a Wi-Fi Aware transport. + */ + public static final int TRANSPORT_WIFI_AWARE = 5; + + /** + * Indicates this network uses a LoWPAN transport. + */ + public static final int TRANSPORT_LOWPAN = 6; + + /** + * Indicates this network uses a Test-only virtual interface as a transport. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int TRANSPORT_TEST = 7; + + /** @hide */ + public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR; + /** @hide */ + public static final int MAX_TRANSPORT = TRANSPORT_TEST; + + /** @hide */ + public static boolean isValidTransport(@Transport int transportType) { + return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT); + } + + private static final String[] TRANSPORT_NAMES = { + "CELLULAR", + "WIFI", + "BLUETOOTH", + "ETHERNET", + "VPN", + "WIFI_AWARE", + "LOWPAN", + "TEST" + }; + + /** + * Allowed transports on a test network, in addition to TRANSPORT_TEST. + */ + private static final int TEST_NETWORKS_ALLOWED_TRANSPORTS = 1 << TRANSPORT_TEST + // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces + | 1 << TRANSPORT_ETHERNET + // Test VPN networks can be created but their UID ranges must be empty. + | 1 << TRANSPORT_VPN; + + /** + * Adds the given transport type to this {@code NetworkCapability} instance. + * Multiple transports may be applied. Note that when searching + * for a network to satisfy a request, any listed in the request will satisfy the request. + * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a + * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network + * to be selected. This is logically different than + * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above. + * + * @param transportType the transport type to be added. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities addTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mTransportTypes |= 1 << transportType; + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking + return this; + } + + /** + * Removes (if found) the given transport from this {@code NetworkCapability} instance. + * + * @param transportType the transport type to be removed. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities removeTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mTransportTypes &= ~(1 << transportType); + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking + return this; + } + + /** + * Sets (or clears) the given transport on this {@link NetworkCapabilities} + * instance. + * + * @hide + */ + public @NonNull NetworkCapabilities setTransportType(@Transport int transportType, + boolean value) { + if (value) { + addTransportType(transportType); + } else { + removeTransportType(transportType); + } + return this; + } + + /** + * Gets all the transports set on this {@code NetworkCapability} instance. + * + * @return an array of transport type values for this instance. + * @hide + */ + @SystemApi + @NonNull public @Transport int[] getTransportTypes() { + return BitUtils.unpackBits(mTransportTypes); + } + + /** + * Sets all the transports set on this {@code NetworkCapability} instance. + * This overwrites any existing transports. + * + * @hide + */ + public void setTransportTypes(@Transport int[] transportTypes) { + mTransportTypes = BitUtils.packBits(transportTypes); + } + + /** + * Tests for the presence of a transport on this instance. + * + * @param transportType the transport type to be tested for. + * @return {@code true} if set on this instance. + */ + public boolean hasTransport(@Transport int transportType) { + return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0); + } + + private void combineTransportTypes(NetworkCapabilities nc) { + this.mTransportTypes |= nc.mTransportTypes; + } + + private boolean satisfiedByTransportTypes(NetworkCapabilities nc) { + return ((this.mTransportTypes == 0) + || ((this.mTransportTypes & nc.mTransportTypes) != 0)); + } + + /** @hide */ + public boolean equalsTransportTypes(NetworkCapabilities nc) { + return (nc.mTransportTypes == this.mTransportTypes); + } + + /** + * UID of the app that owns this network, or Process#INVALID_UID if none/unknown. + * + *

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. + * + *

For NetworkCapability instances being sent from ConnectivityService, this value MUST be + * reset to Process.INVALID_UID unless all the following conditions are met: + * + *

The caller is the network owner, AND one of the following sets of requirements is met: + * + *

    + *
  1. The described Network is a VPN + *
+ * + *

OR: + * + *

    + *
  1. The calling app is the network owner + *
  2. The calling app has the ACCESS_FINE_LOCATION permission granted + *
  3. The user's location toggle is on + *
+ * + * This is because the owner UID is location-sensitive. The apps that request a network could + * know where the device is if they can tell for sure the system has connected to the network + * they requested. + * + *

This is populated by the network agents and for the NetworkCapabilities instance sent by + * an app to the System Server, the value MUST be reset to Process.INVALID_UID by the system + * server. + */ + private int mOwnerUid = Process.INVALID_UID; + + /** + * Set the UID of the owner app. + * @hide + */ + public @NonNull NetworkCapabilities setOwnerUid(final int uid) { + mOwnerUid = uid; + return this; + } + + /** + * Retrieves the UID of the app that owns this network. + * + *

For user privacy reasons, this field will only be populated if the following conditions + * are met: + * + *

The caller is the network owner, AND one of the following sets of requirements is met: + * + *

    + *
  1. The described Network is a VPN + *
+ * + *

OR: + * + *

    + *
  1. The calling app is the network owner + *
  2. The calling app has the ACCESS_FINE_LOCATION permission granted + *
  3. The user's location toggle is on + *
+ * + * Instances of NetworkCapabilities sent to apps without the appropriate permissions will have + * this field cleared out. + */ + public int getOwnerUid() { + return mOwnerUid; + } + + private boolean equalsOwnerUid(@NonNull final NetworkCapabilities nc) { + return mOwnerUid == nc.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+. + * + *

When received from an app in a NetworkRequest this is always cleared out by the system + * server. This field is never used for matching NetworkRequests to NetworkAgents. + */ + @NonNull private int[] mAdministratorUids = new int[0]; + + /** + * Sets the int[] 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: + * + *

    + *
  • Carrier apps with privileges for the relevant subscription + *
  • Active VPN apps + *
  • Other application groups with a particular Network-related role + *
+ * + *

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. The owner UID MUST + * always be included in administratorUids. + * + *

The administrator UIDs are set by network agents. + * + * @param administratorUids the UIDs to be set as administrators of this Network. + * @throws IllegalArgumentException if duplicate UIDs are contained in administratorUids + * @see #mAdministratorUids + * @hide + */ + @NonNull + public NetworkCapabilities setAdministratorUids(@NonNull final int[] administratorUids) { + mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length); + Arrays.sort(mAdministratorUids); + for (int i = 0; i < mAdministratorUids.length - 1; i++) { + if (mAdministratorUids[i] >= mAdministratorUids[i + 1]) { + throw new IllegalArgumentException("All administrator UIDs must be unique"); + } + } + return this; + } + + /** + * Retrieves the UIDs that are administrators of this Network. + * + *

This is only populated in NetworkCapabilities objects that come from network agents for + * networks that are managed by specific apps on the system, such as carrier privileged apps or + * wifi suggestion apps. This will include the network owner. + * + * @return the int[] of UIDs that are administrators of this Network + * @see #mAdministratorUids + * @hide + */ + @NonNull + @SystemApi + public int[] getAdministratorUids() { + return Arrays.copyOf(mAdministratorUids, mAdministratorUids.length); + } + + /** + * Tests if the set of administrator UIDs of this network is the same as that of the passed one. + * + *

The administrator UIDs must be in sorted order. + * + *

nc is assumed non-null. Else, NPE. + * + * @hide + */ + @VisibleForTesting(visibility = PRIVATE) + public boolean equalsAdministratorUids(@NonNull final NetworkCapabilities nc) { + return Arrays.equals(mAdministratorUids, nc.mAdministratorUids); + } + + /** + * Combine the administrator UIDs of the capabilities. + * + *

This is only legal if either of the administrators lists are empty, or if they are equal. + * Combining administrator UIDs is only possible for combining non-overlapping sets of UIDs. + * + *

If both administrator lists are non-empty but not equal, they conflict with each other. In + * this case, it would not make sense to add them together. + */ + private void combineAdministratorUids(@NonNull final NetworkCapabilities nc) { + if (nc.mAdministratorUids.length == 0) return; + if (mAdministratorUids.length == 0) { + mAdministratorUids = Arrays.copyOf(nc.mAdministratorUids, nc.mAdministratorUids.length); + return; + } + if (!equalsAdministratorUids(nc)) { + throw new IllegalStateException("Can't combine two different administrator UID lists"); + } + } + + /** + * Value indicating that link bandwidth is unspecified. + * @hide + */ + public static final int LINK_BANDWIDTH_UNSPECIFIED = 0; + + /** + * Passive link bandwidth. This is a rough guide of the expected peak bandwidth + * for the first hop on the given transport. It is not measured, but may take into account + * link parameters (Radio technology, allocated channels, etc). + */ + private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + + /** + * Sets the upstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * {@see Builder#setLinkUpstreamBandwidthKbps} + * + * @param upKbps the estimated first hop upstream (device to network) bandwidth. + * @hide + */ + public @NonNull NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) { + mLinkUpBandwidthKbps = upKbps; + return this; + } + + /** + * Retrieves the upstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + * + * @return The estimated first hop upstream (device to network) bandwidth. + */ + public int getLinkUpstreamBandwidthKbps() { + return mLinkUpBandwidthKbps; + } + + /** + * Sets the downstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * {@see Builder#setLinkUpstreamBandwidthKbps} + * + * @param downKbps the estimated first hop downstream (network to device) bandwidth. + * @hide + */ + public @NonNull NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) { + mLinkDownBandwidthKbps = downKbps; + return this; + } + + /** + * Retrieves the downstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + * + * @return The estimated first hop downstream (network to device) bandwidth. + */ + public int getLinkDownstreamBandwidthKbps() { + return mLinkDownBandwidthKbps; + } + + private void combineLinkBandwidths(NetworkCapabilities nc) { + this.mLinkUpBandwidthKbps = + Math.max(this.mLinkUpBandwidthKbps, nc.mLinkUpBandwidthKbps); + this.mLinkDownBandwidthKbps = + Math.max(this.mLinkDownBandwidthKbps, nc.mLinkDownBandwidthKbps); + } + private boolean satisfiedByLinkBandwidths(NetworkCapabilities nc) { + return !(this.mLinkUpBandwidthKbps > nc.mLinkUpBandwidthKbps + || this.mLinkDownBandwidthKbps > nc.mLinkDownBandwidthKbps); + } + private boolean equalsLinkBandwidths(NetworkCapabilities nc) { + return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps + && this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps); + } + /** @hide */ + public static int minBandwidth(int a, int b) { + if (a == LINK_BANDWIDTH_UNSPECIFIED) { + return b; + } else if (b == LINK_BANDWIDTH_UNSPECIFIED) { + return a; + } else { + return Math.min(a, b); + } + } + /** @hide */ + public static int maxBandwidth(int a, int b) { + return Math.max(a, b); + } + + private NetworkSpecifier mNetworkSpecifier = null; + private TransportInfo mTransportInfo = null; + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * + * @param networkSpecifier A concrete, parcelable framework class that extends + * NetworkSpecifier. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities setNetworkSpecifier( + @NonNull NetworkSpecifier networkSpecifier) { + if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) { + throw new IllegalStateException("Must have a single transport specified to use " + + "setNetworkSpecifier"); + } + + mNetworkSpecifier = networkSpecifier; + + return this; + } + + /** + * Sets the optional transport specific information. + * + * @param transportInfo A concrete, parcelable framework class that extends + * {@link TransportInfo}. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities setTransportInfo(@NonNull TransportInfo transportInfo) { + mTransportInfo = transportInfo; + return this; + } + + /** + * Gets the optional bearer specific network specifier. May be {@code null} if not set. + * + * @return The optional {@link NetworkSpecifier} specifying the bearer specific network + * specifier or {@code null}. + */ + public @Nullable NetworkSpecifier getNetworkSpecifier() { + return mNetworkSpecifier; + } + + /** + * Returns a transport-specific information container. The application may cast this + * container to a concrete sub-class based on its knowledge of the network request. The + * application should be able to deal with a {@code null} return value or an invalid case, + * e.g. use {@code instanceof} operator to verify expected type. + * + * @return A concrete implementation of the {@link TransportInfo} class or null if not + * available for the network. + */ + @Nullable public TransportInfo getTransportInfo() { + return mTransportInfo; + } + + private void combineSpecifiers(NetworkCapabilities nc) { + if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) { + throw new IllegalStateException("Can't combine two networkSpecifiers"); + } + setNetworkSpecifier(nc.mNetworkSpecifier); + } + + private boolean satisfiedBySpecifier(NetworkCapabilities nc) { + return mNetworkSpecifier == null || mNetworkSpecifier.canBeSatisfiedBy(nc.mNetworkSpecifier) + || nc.mNetworkSpecifier instanceof MatchAllNetworkSpecifier; + } + + private boolean equalsSpecifier(NetworkCapabilities nc) { + return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier); + } + + private void combineTransportInfos(NetworkCapabilities nc) { + if (mTransportInfo != null && !mTransportInfo.equals(nc.mTransportInfo)) { + throw new IllegalStateException("Can't combine two TransportInfos"); + } + setTransportInfo(nc.mTransportInfo); + } + + private boolean equalsTransportInfo(NetworkCapabilities nc) { + return Objects.equals(mTransportInfo, nc.mTransportInfo); + } + + /** + * Magic value that indicates no signal strength provided. A request specifying this value is + * always satisfied. + */ + public static final int SIGNAL_STRENGTH_UNSPECIFIED = Integer.MIN_VALUE; + + /** + * Signal strength. This is a signed integer, and higher values indicate better signal. + * The exact units are bearer-dependent. For example, Wi-Fi uses RSSI. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + private int mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; + + /** + * Sets the signal strength. This is a signed integer, with higher values indicating a stronger + * signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same RSSI units + * reported by wifi code. + *

+ * Note that when used to register a network callback, this specifies the minimum acceptable + * signal strength. When received as the state of an existing network it specifies the current + * value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has no + * effect when requesting a callback. + * + * @param signalStrength the bearer-specific signal strength. + * @hide + */ + public @NonNull NetworkCapabilities setSignalStrength(int signalStrength) { + mSignalStrength = signalStrength; + return this; + } + + /** + * Returns {@code true} if this object specifies a signal strength. + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public boolean hasSignalStrength() { + return mSignalStrength > SIGNAL_STRENGTH_UNSPECIFIED; + } + + /** + * Retrieves the signal strength. + * + * @return The bearer-specific signal strength. + */ + public int getSignalStrength() { + return mSignalStrength; + } + + private void combineSignalStrength(NetworkCapabilities nc) { + this.mSignalStrength = Math.max(this.mSignalStrength, nc.mSignalStrength); + } + + private boolean satisfiedBySignalStrength(NetworkCapabilities nc) { + return this.mSignalStrength <= nc.mSignalStrength; + } + + private boolean equalsSignalStrength(NetworkCapabilities nc) { + return this.mSignalStrength == nc.mSignalStrength; + } + + /** + * List of UIDs this network applies to. No restriction if null. + *

+ * For networks, mUids represent the list of network this applies to, and null means this + * network applies to all UIDs. + * For requests, mUids is the list of UIDs this network MUST apply to to match ; ALL UIDs + * must be included in a network so that they match. As an exception to the general rule, + * a null mUids field for requests mean "no requirements" rather than what the general rule + * would suggest ("must apply to all UIDs") : this is because this has shown to be what users + * of this API expect in practice. A network that must match all UIDs can still be + * expressed with a set ranging the entire set of possible UIDs. + *

+ * mUids is typically (and at this time, only) used by VPN. This network is only available to + * the UIDs in this list, and it is their default network. Apps in this list that wish to + * bypass the VPN can do so iff the VPN app allows them to or if they are privileged. If this + * 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 + * 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. + *

+ * Please note that in principle a single app can be associated with multiple UIDs because + * each app will have a different UID when it's run as a different (macro-)user. A single + * macro user can only have a single active VPN app at any given time however. + *

+ * Also please be aware this class does not try to enforce any normalization on this. Callers + * can only alter the UIDs by setting them wholesale : this class does not provide any utility + * to add or remove individual UIDs or ranges. If callers have any normalization needs on + * their own (like requiring sortedness or no overlap) they need to enforce it + * themselves. Some of the internal methods also assume this is normalized as in no adjacent + * or overlapping ranges are present. + * + * @hide + */ + private ArraySet mUids = null; + + /** + * Convenience method to set the UIDs this network applies to to a single UID. + * @hide + */ + public @NonNull NetworkCapabilities setSingleUid(int uid) { + final ArraySet identity = new ArraySet<>(1); + identity.add(new UidRange(uid, uid)); + setUids(identity); + return this; + } + + /** + * Set the list of UIDs this network applies to. + * This makes a copy of the set so that callers can't modify it after the call. + * @hide + */ + public @NonNull NetworkCapabilities setUids(Set uids) { + if (null == uids) { + mUids = null; + } else { + mUids = new ArraySet<>(uids); + } + return this; + } + + /** + * Get the list of UIDs this network applies to. + * This returns a copy of the set so that callers can't modify the original object. + * @hide + */ + public @Nullable Set getUids() { + return null == mUids ? null : new ArraySet<>(mUids); + } + + /** + * Test whether this network applies to this UID. + * @hide + */ + public boolean appliesToUid(int uid) { + if (null == mUids) return true; + for (UidRange range : mUids) { + if (range.contains(uid)) { + return true; + } + } + return false; + } + + /** + * Tests if the set of UIDs that this network applies to is the same as the passed network. + *

+ * This test only checks whether equal range objects are in both sets. It will + * return false if the ranges are not exactly the same, even if the covered UIDs + * are for an equivalent result. + *

+ * Note that this method is not very optimized, which is fine as long as it's not used very + * often. + *

+ * nc is assumed nonnull. + * + * @hide + */ + @VisibleForTesting + public boolean equalsUids(@NonNull NetworkCapabilities nc) { + Set comparedUids = nc.mUids; + if (null == comparedUids) return null == mUids; + if (null == mUids) return false; + // Make a copy so it can be mutated to check that all ranges in mUids + // also are in uids. + final Set uids = new ArraySet<>(mUids); + for (UidRange range : comparedUids) { + if (!uids.contains(range)) { + return false; + } + uids.remove(range); + } + return uids.isEmpty(); + } + + /** + * Test whether the passed NetworkCapabilities satisfies the UIDs this capabilities require. + * + * This method is called on the NetworkCapabilities embedded in a request with the + * capabilities of an available network. It checks whether all the UIDs from this listen + * (representing the UIDs that must have access to the network) are satisfied by the UIDs + * 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 + * 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. + *

+ * nc is assumed nonnull. Else, NPE. + * @see #appliesToUid + * @hide + */ + 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.mOwnerUid)) return true; + if (!nc.appliesToUidRange(requiredRange)) { + return false; + } + } + return true; + } + + /** + * Returns whether this network applies to the passed ranges. + * This assumes that to apply, the passed range has to be entirely contained + * within one of the ranges this network applies to. If the ranges are not normalized, + * this method may return false even though all required UIDs are covered because no + * single range contained them all. + * @hide + */ + @VisibleForTesting + public boolean appliesToUidRange(@Nullable UidRange requiredRange) { + if (null == mUids) return true; + for (UidRange uidRange : mUids) { + if (uidRange.containsRange(requiredRange)) { + return true; + } + } + return false; + } + + /** + * Combine the UIDs this network currently applies to with the UIDs the passed + * NetworkCapabilities apply to. + * nc is assumed nonnull. + */ + private void combineUids(@NonNull NetworkCapabilities nc) { + if (null == nc.mUids || null == mUids) { + mUids = null; + return; + } + mUids.addAll(nc.mUids); + } + + + /** + * The SSID of the network, or null if not applicable or unknown. + *

+ * This is filled in by wifi code. + * @hide + */ + private String mSSID; + + /** + * Sets the SSID of this network. + * @hide + */ + public @NonNull NetworkCapabilities setSSID(@Nullable String ssid) { + mSSID = ssid; + return this; + } + + /** + * Gets the SSID of this network, or null if none or unknown. + * @hide + */ + @SystemApi + public @Nullable String getSsid() { + return mSSID; + } + + /** + * Tests if the SSID of this network is the same as the SSID of the passed network. + * @hide + */ + public boolean equalsSSID(@NonNull NetworkCapabilities nc) { + return Objects.equals(mSSID, nc.mSSID); + } + + /** + * Check if the SSID requirements of this object are matched by the passed object. + * @hide + */ + public boolean satisfiedBySSID(@NonNull NetworkCapabilities nc) { + return mSSID == null || mSSID.equals(nc.mSSID); + } + + /** + * Combine SSIDs of the capabilities. + *

+ * This is only legal if either the SSID of this object is null, or both SSIDs are + * equal. + * @hide + */ + private void combineSSIDs(@NonNull NetworkCapabilities nc) { + if (mSSID != null && !mSSID.equals(nc.mSSID)) { + throw new IllegalStateException("Can't combine two SSIDs"); + } + setSSID(nc.mSSID); + } + + /** + * Combine a set of Capabilities to this one. Useful for coming up with the complete set. + *

+ * Note that this method may break an invariant of having a particular capability in either + * wanted or unwanted lists but never in both. Requests that have the same capability in + * both lists will never be satisfied. + * @hide + */ + public void combineCapabilities(@NonNull NetworkCapabilities nc) { + combineNetCapabilities(nc); + combineTransportTypes(nc); + combineLinkBandwidths(nc); + combineSpecifiers(nc); + combineTransportInfos(nc); + combineSignalStrength(nc); + combineUids(nc); + combineSSIDs(nc); + combineRequestor(nc); + combineAdministratorUids(nc); + } + + /** + * Check if our requirements are satisfied by the given {@code NetworkCapabilities}. + * + * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements. + * @param onlyImmutable if {@code true}, do not consider mutable requirements such as link + * bandwidth, signal strength, or validation / captive portal status. + * + * @hide + */ + private boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc, boolean onlyImmutable) { + return (nc != null + && satisfiedByNetCapabilities(nc, onlyImmutable) + && satisfiedByTransportTypes(nc) + && (onlyImmutable || satisfiedByLinkBandwidths(nc)) + && satisfiedBySpecifier(nc) + && (onlyImmutable || satisfiedBySignalStrength(nc)) + && (onlyImmutable || satisfiedByUids(nc)) + && (onlyImmutable || satisfiedBySSID(nc))) + && (onlyImmutable || satisfiedByRequestor(nc)); + } + + /** + * Check if our requirements are satisfied by the given {@code NetworkCapabilities}. + * + * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements. + * + * @hide + */ + @SystemApi + public boolean satisfiedByNetworkCapabilities(@Nullable NetworkCapabilities nc) { + return satisfiedByNetworkCapabilities(nc, false); + } + + /** + * Check if our immutable requirements are satisfied by the given {@code NetworkCapabilities}. + * + * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements. + * + * @hide + */ + public boolean satisfiedByImmutableNetworkCapabilities(@Nullable NetworkCapabilities nc) { + return satisfiedByNetworkCapabilities(nc, true); + } + + /** + * Checks that our immutable capabilities are the same as those of the given + * {@code NetworkCapabilities} and return a String describing any difference. + * The returned String is empty if there is no difference. + * + * @hide + */ + public String describeImmutableDifferences(@Nullable NetworkCapabilities that) { + if (that == null) { + return "other NetworkCapabilities was null"; + } + + StringJoiner joiner = new StringJoiner(", "); + + // Ignore NOT_METERED being added or removed as it is effectively dynamic. http://b/63326103 + // TODO: properly support NOT_METERED as a mutable and requestable capability. + final long mask = ~MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_NOT_METERED); + long oldImmutableCapabilities = this.mNetworkCapabilities & mask; + long newImmutableCapabilities = that.mNetworkCapabilities & mask; + if (oldImmutableCapabilities != newImmutableCapabilities) { + String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities)); + String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities)); + joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after)); + } + + if (!equalsSpecifier(that)) { + NetworkSpecifier before = this.getNetworkSpecifier(); + NetworkSpecifier after = that.getNetworkSpecifier(); + joiner.add(String.format("specifier changed: %s -> %s", before, after)); + } + + if (!equalsTransportTypes(that)) { + String before = transportNamesOf(this.getTransportTypes()); + String after = transportNamesOf(that.getTransportTypes()); + joiner.add(String.format("transports changed: %s -> %s", before, after)); + } + + return joiner.toString(); + } + + /** + * Checks that our requestable capabilities are the same as those of the given + * {@code NetworkCapabilities}. + * + * @hide + */ + public boolean equalRequestableCapabilities(@Nullable NetworkCapabilities nc) { + if (nc == null) return false; + return (equalsNetCapabilitiesRequestable(nc) + && equalsTransportTypes(nc) + && equalsSpecifier(nc)); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null || (obj instanceof NetworkCapabilities == false)) return false; + NetworkCapabilities that = (NetworkCapabilities) obj; + return equalsNetCapabilities(that) + && equalsTransportTypes(that) + && equalsLinkBandwidths(that) + && equalsSignalStrength(that) + && equalsSpecifier(that) + && equalsTransportInfo(that) + && equalsUids(that) + && equalsSSID(that) + && equalsOwnerUid(that) + && equalsPrivateDnsBroken(that) + && equalsRequestor(that) + && equalsAdministratorUids(that); + } + + @Override + public int hashCode() { + return (int) (mNetworkCapabilities & 0xFFFFFFFF) + + ((int) (mNetworkCapabilities >> 32) * 3) + + ((int) (mUnwantedNetworkCapabilities & 0xFFFFFFFF) * 5) + + ((int) (mUnwantedNetworkCapabilities >> 32) * 7) + + ((int) (mTransportTypes & 0xFFFFFFFF) * 11) + + ((int) (mTransportTypes >> 32) * 13) + + mLinkUpBandwidthKbps * 17 + + mLinkDownBandwidthKbps * 19 + + Objects.hashCode(mNetworkSpecifier) * 23 + + mSignalStrength * 29 + + mOwnerUid * 31 + + Objects.hashCode(mUids) * 37 + + Objects.hashCode(mSSID) * 41 + + Objects.hashCode(mTransportInfo) * 43 + + Objects.hashCode(mPrivateDnsBroken) * 47 + + Objects.hashCode(mRequestorUid) * 53 + + Objects.hashCode(mRequestorPackageName) * 59 + + Arrays.hashCode(mAdministratorUids) * 61; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mNetworkCapabilities); + dest.writeLong(mUnwantedNetworkCapabilities); + dest.writeLong(mTransportTypes); + dest.writeInt(mLinkUpBandwidthKbps); + dest.writeInt(mLinkDownBandwidthKbps); + dest.writeParcelable((Parcelable) mNetworkSpecifier, flags); + dest.writeParcelable((Parcelable) mTransportInfo, flags); + dest.writeInt(mSignalStrength); + dest.writeArraySet(mUids); + dest.writeString(mSSID); + dest.writeBoolean(mPrivateDnsBroken); + dest.writeIntArray(getAdministratorUids()); + dest.writeInt(mOwnerUid); + dest.writeInt(mRequestorUid); + dest.writeString(mRequestorPackageName); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + @Override + public NetworkCapabilities createFromParcel(Parcel in) { + NetworkCapabilities netCap = new NetworkCapabilities(); + + netCap.mNetworkCapabilities = in.readLong(); + netCap.mUnwantedNetworkCapabilities = in.readLong(); + netCap.mTransportTypes = in.readLong(); + netCap.mLinkUpBandwidthKbps = in.readInt(); + netCap.mLinkDownBandwidthKbps = in.readInt(); + netCap.mNetworkSpecifier = in.readParcelable(null); + netCap.mTransportInfo = in.readParcelable(null); + netCap.mSignalStrength = in.readInt(); + netCap.mUids = (ArraySet) in.readArraySet( + null /* ClassLoader, null for default */); + netCap.mSSID = in.readString(); + netCap.mPrivateDnsBroken = in.readBoolean(); + netCap.setAdministratorUids(in.createIntArray()); + netCap.mOwnerUid = in.readInt(); + netCap.mRequestorUid = in.readInt(); + netCap.mRequestorPackageName = in.readString(); + return netCap; + } + @Override + public NetworkCapabilities[] newArray(int size) { + return new NetworkCapabilities[size]; + } + }; + + @Override + public @NonNull String toString() { + final StringBuilder sb = new StringBuilder("["); + if (0 != mTransportTypes) { + sb.append(" Transports: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mTransportTypes, + NetworkCapabilities::transportNameOf, "|"); + } + if (0 != mNetworkCapabilities) { + sb.append(" Capabilities: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities, + NetworkCapabilities::capabilityNameOf, "&"); + } + if (0 != mUnwantedNetworkCapabilities) { + sb.append(" Unwanted: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities, + NetworkCapabilities::capabilityNameOf, "&"); + } + if (mLinkUpBandwidthKbps > 0) { + sb.append(" LinkUpBandwidth>=").append(mLinkUpBandwidthKbps).append("Kbps"); + } + if (mLinkDownBandwidthKbps > 0) { + sb.append(" LinkDnBandwidth>=").append(mLinkDownBandwidthKbps).append("Kbps"); + } + if (mNetworkSpecifier != null) { + sb.append(" Specifier: <").append(mNetworkSpecifier).append(">"); + } + if (mTransportInfo != null) { + sb.append(" TransportInfo: <").append(mTransportInfo).append(">"); + } + if (hasSignalStrength()) { + sb.append(" SignalStrength: ").append(mSignalStrength); + } + + if (null != mUids) { + if ((1 == mUids.size()) && (mUids.valueAt(0).count() == 1)) { + sb.append(" Uid: ").append(mUids.valueAt(0).start); + } else { + sb.append(" Uids: <").append(mUids).append(">"); + } + } + if (mOwnerUid != Process.INVALID_UID) { + sb.append(" OwnerUid: ").append(mOwnerUid); + } + + if (!ArrayUtils.isEmpty(mAdministratorUids)) { + sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids)); + } + + if (mRequestorUid != Process.INVALID_UID) { + sb.append(" RequestorUid: ").append(mRequestorUid); + } + + if (mRequestorPackageName != null) { + sb.append(" RequestorPkg: ").append(mRequestorPackageName); + } + + if (null != mSSID) { + sb.append(" SSID: ").append(mSSID); + } + + + if (mPrivateDnsBroken) { + sb.append(" PrivateDnsBroken"); + } + + sb.append("]"); + return sb.toString(); + } + + + private interface NameOf { + String nameOf(int value); + } + + /** + * @hide + */ + public static void appendStringRepresentationOfBitMaskToStringBuilder(@NonNull StringBuilder sb, + long bitMask, @NonNull NameOf nameFetcher, @NonNull String separator) { + int bitPos = 0; + boolean firstElementAdded = false; + while (bitMask != 0) { + if ((bitMask & 1) != 0) { + if (firstElementAdded) { + sb.append(separator); + } else { + firstElementAdded = true; + } + sb.append(nameFetcher.nameOf(bitPos)); + } + bitMask >>= 1; + ++bitPos; + } + } + + /** @hide */ + public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + for (int transport : getTransportTypes()) { + proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport); + } + + for (int capability : getCapabilities()) { + proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability); + } + + proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps); + proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps); + + if (mNetworkSpecifier != null) { + proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString()); + } + if (mTransportInfo != null) { + // TODO b/120653863: write transport-specific info to proto? + } + + proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength()); + proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength); + + proto.end(token); + } + + /** + * @hide + */ + public static @NonNull String capabilityNamesOf(@Nullable @NetCapability int[] capabilities) { + StringJoiner joiner = new StringJoiner("|"); + if (capabilities != null) { + for (int c : capabilities) { + joiner.add(capabilityNameOf(c)); + } + } + return joiner.toString(); + } + + /** + * @hide + */ + public static @NonNull String capabilityNameOf(@NetCapability int capability) { + switch (capability) { + case NET_CAPABILITY_MMS: return "MMS"; + case NET_CAPABILITY_SUPL: return "SUPL"; + case NET_CAPABILITY_DUN: return "DUN"; + case NET_CAPABILITY_FOTA: return "FOTA"; + case NET_CAPABILITY_IMS: return "IMS"; + case NET_CAPABILITY_CBS: return "CBS"; + case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P"; + case NET_CAPABILITY_IA: return "IA"; + case NET_CAPABILITY_RCS: return "RCS"; + case NET_CAPABILITY_XCAP: return "XCAP"; + case NET_CAPABILITY_EIMS: return "EIMS"; + case NET_CAPABILITY_NOT_METERED: return "NOT_METERED"; + case NET_CAPABILITY_INTERNET: return "INTERNET"; + case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED"; + case NET_CAPABILITY_TRUSTED: return "TRUSTED"; + case NET_CAPABILITY_NOT_VPN: return "NOT_VPN"; + case NET_CAPABILITY_VALIDATED: return "VALIDATED"; + case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL"; + case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING"; + case NET_CAPABILITY_FOREGROUND: return "FOREGROUND"; + case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED"; + case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED"; + case NET_CAPABILITY_OEM_PAID: return "OEM_PAID"; + case NET_CAPABILITY_MCX: return "MCX"; + case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; + case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; + case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE"; + case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL"; + case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; + default: return Integer.toString(capability); + } + } + + /** + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static @NonNull String transportNamesOf(@Nullable @Transport int[] types) { + StringJoiner joiner = new StringJoiner("|"); + if (types != null) { + for (int t : types) { + joiner.add(transportNameOf(t)); + } + } + return joiner.toString(); + } + + /** + * @hide + */ + public static @NonNull String transportNameOf(@Transport int transport) { + if (!isValidTransport(transport)) { + return "UNKNOWN"; + } + return TRANSPORT_NAMES[transport]; + } + + private static void checkValidTransportType(@Transport int transport) { + Preconditions.checkArgument( + isValidTransport(transport), "Invalid TransportType " + transport); + } + + private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) { + return capability >= MIN_NET_CAPABILITY && capability <= MAX_NET_CAPABILITY; + } + + private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { + Preconditions.checkArgument(isValidCapability(capability), + "NetworkCapability " + capability + "out of range"); + } + + /** + * Check if this {@code NetworkCapability} instance is metered. + * + * @return {@code true} if {@code NET_CAPABILITY_NOT_METERED} is not set on this instance. + * @hide + */ + public boolean isMetered() { + return !hasCapability(NET_CAPABILITY_NOT_METERED); + } + + /** + * Check if private dns is broken. + * + * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken. + * @hide + */ + public boolean isPrivateDnsBroken() { + return mPrivateDnsBroken; + } + + /** + * Set mPrivateDnsBroken to true when private dns is broken. + * + * @param broken the status of private DNS to be set. + * @hide + */ + public void setPrivateDnsBroken(boolean broken) { + mPrivateDnsBroken = broken; + } + + private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) { + return mPrivateDnsBroken == nc.mPrivateDnsBroken; + } + + /** + * Set the UID of the app making the request. + * + * For instances of NetworkCapabilities representing a request, sets the + * UID of the app making the request. For a network created by the system, + * sets the UID of the only app whose requests can match this network. + * This can be set to {@link Process#INVALID_UID} if there is no such app, + * or if this instance of NetworkCapabilities is about to be sent to a + * party that should not learn about this. + * + * @param uid UID of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorUid(int uid) { + mRequestorUid = uid; + return this; + } + + /** + * Returns the UID of the app making the request. + * + * For a NetworkRequest being made by an app, contains the app's UID. For a network + * created by the system, contains the UID of the only app whose requests can match + * this network, or {@link Process#INVALID_UID} if none or if the + * caller does not have permission to learn about this. + * + * @return the uid of the app making the request. + * @hide + */ + public int getRequestorUid() { + return mRequestorUid; + } + + /** + * Set the package name of the app making the request. + * + * For instances of NetworkCapabilities representing a request, sets the + * package name of the app making the request. For a network created by the system, + * sets the package name of the only app whose requests can match this network. + * This can be set to null if there is no such app, or if this instance of + * NetworkCapabilities is about to be sent to a party that should not learn about this. + * + * @param packageName package name of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorPackageName(@NonNull String packageName) { + mRequestorPackageName = packageName; + return this; + } + + /** + * Returns the package name of the app making the request. + * + * For a NetworkRequest being made by an app, contains the app's package name. For a + * network created by the system, contains the package name of the only app whose + * requests can match this network, or null if none or if the caller does not have + * permission to learn about this. + * + * @return the package name of the app making the request. + * @hide + */ + @Nullable + public String getRequestorPackageName() { + return mRequestorPackageName; + } + + /** + * Set the uid and package name of the app causing this network to exist. + * + * {@see #setRequestorUid} and {@link #setRequestorPackageName} + * + * @param uid UID of the app. + * @param packageName package name of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorUidAndPackageName( + int uid, @NonNull String packageName) { + return setRequestorUid(uid).setRequestorPackageName(packageName); + } + + /** + * Test whether the passed NetworkCapabilities satisfies the requestor restrictions of this + * capabilities. + * + * This method is called on the NetworkCapabilities embedded in a request with the + * capabilities of an available network. If the available network, sets a specific + * requestor (by uid and optionally package name), then this will only match a request from the + * same app. If either of the capabilities have an unset uid or package name, then it matches + * everything. + *

+ * nc is assumed nonnull. Else, NPE. + */ + private boolean satisfiedByRequestor(NetworkCapabilities nc) { + // No uid set, matches everything. + if (mRequestorUid == Process.INVALID_UID || nc.mRequestorUid == Process.INVALID_UID) { + return true; + } + // uids don't match. + if (mRequestorUid != nc.mRequestorUid) return false; + // No package names set, matches everything + if (null == nc.mRequestorPackageName || null == mRequestorPackageName) return true; + // check for package name match. + return TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } + + /** + * Combine requestor info of the capabilities. + *

+ * This is only legal if either the requestor info of this object is reset, or both info are + * equal. + * nc is assumed nonnull. + */ + private void combineRequestor(@NonNull NetworkCapabilities nc) { + if (mRequestorUid != Process.INVALID_UID && mRequestorUid != nc.mOwnerUid) { + throw new IllegalStateException("Can't combine two uids"); + } + if (mRequestorPackageName != null + && !mRequestorPackageName.equals(nc.mRequestorPackageName)) { + throw new IllegalStateException("Can't combine two package names"); + } + setRequestorUid(nc.mRequestorUid); + setRequestorPackageName(nc.mRequestorPackageName); + } + + private boolean equalsRequestor(NetworkCapabilities nc) { + return mRequestorUid == nc.mRequestorUid + && TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } + + /** + * Builder class for NetworkCapabilities. + * + * This class is mainly for for {@link NetworkAgent} instances to use. Many fields in + * the built class require holding a signature permission to use - mostly + * {@link android.Manifest.permission.NETWORK_FACTORY}, but refer to the specific + * description of each setter. As this class lives entirely in app space it does not + * enforce these restrictions itself but the system server clears out the relevant + * fields when receiving a NetworkCapabilities object from a caller without the + * appropriate permission. + * + * Apps don't use this builder directly. Instead, they use {@link NetworkRequest} via + * its builder object. + * + * @hide + */ + @SystemApi + public static final class Builder { + private final NetworkCapabilities mCaps; + + /** + * Creates a new Builder to construct NetworkCapabilities objects. + */ + public Builder() { + mCaps = new NetworkCapabilities(); + } + + /** + * Creates a new Builder of NetworkCapabilities from an existing instance. + */ + public Builder(@NonNull final NetworkCapabilities nc) { + Objects.requireNonNull(nc); + mCaps = new NetworkCapabilities(nc); + } + + /** + * Adds the given transport type. + * + * Multiple transports may be added. Note that when searching for a network to satisfy a + * request, satisfying any of the transports listed in the request will satisfy the request. + * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a + * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network + * to be selected. This is logically different than + * {@code NetworkCapabilities.NET_CAPABILITY_*}. + * + * @param transportType the transport type to be added or removed. + * @return this builder + */ + @NonNull + public Builder addTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mCaps.addTransportType(transportType); + return this; + } + + /** + * Removes the given transport type. + * + * {@see #addTransportType}. + * + * @param transportType the transport type to be added or removed. + * @return this builder + */ + @NonNull + public Builder removeTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mCaps.removeTransportType(transportType); + return this; + } + + /** + * Adds the given capability. + * + * @param capability the capability + * @return this builder + */ + @NonNull + public Builder addCapability(@NetCapability final int capability) { + mCaps.setCapability(capability, true); + return this; + } + + /** + * Removes the given capability. + * + * @param capability the capability + * @return this builder + */ + @NonNull + public Builder removeCapability(@NetCapability final int capability) { + mCaps.setCapability(capability, false); + return this; + } + + /** + * Sets the owner UID. + * + * The default value is {@link Process#INVALID_UID}. Pass this value to reset. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source. + * + * @param ownerUid the owner UID + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setOwnerUid(final int ownerUid) { + mCaps.setOwnerUid(ownerUid); + return this; + } + + /** + * 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: + *

    + *
  • Carrier apps with privileges for the relevant subscription + *
  • Active VPN apps + *
  • Other application groups with a particular Network-related role + *
+ * + *

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

An app is granted owner privileges over Networks that it supplies. The owner + * UID MUST always be included in administratorUids. + * + * The default value is the empty array. Pass an empty array to reset. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source, such as an app using reflection to call this or + * mutate the member in the built object. + * + * @param administratorUids the UIDs to be set as administrators of this Network. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setAdministratorUids(@NonNull final int[] administratorUids) { + Objects.requireNonNull(administratorUids); + mCaps.setAdministratorUids(administratorUids); + return this; + } + + /** + * Sets the upstream bandwidth of the link. + * + * Sets the upstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * Note that when used to request a network, this specifies the minimum acceptable. + * When received as the state of an existing network this specifies the typical + * first hop bandwidth expected. This is never measured, but rather is inferred + * from technology type and other link parameters. It could be used to differentiate + * between very slow 1xRTT cellular links and other faster networks or even between + * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between + * fast backhauls and slow backhauls. + * + * @param upKbps the estimated first hop upstream (device to network) bandwidth. + * @return this builder + */ + @NonNull + public Builder setLinkUpstreamBandwidthKbps(final int upKbps) { + mCaps.setLinkUpstreamBandwidthKbps(upKbps); + return this; + } + + /** + * Sets the downstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * Note that when used to request a network, this specifies the minimum acceptable. + * When received as the state of an existing network this specifies the typical + * first hop bandwidth expected. This is never measured, but rather is inferred + * from technology type and other link parameters. It could be used to differentiate + * between very slow 1xRTT cellular links and other faster networks or even between + * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between + * fast backhauls and slow backhauls. + * + * @param downKbps the estimated first hop downstream (network to device) bandwidth. + * @return this builder + */ + @NonNull + public Builder setLinkDownstreamBandwidthKbps(final int downKbps) { + mCaps.setLinkDownstreamBandwidthKbps(downKbps); + return this; + } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * + * @param specifier a concrete, parcelable framework class that extends NetworkSpecifier, + * or null to clear it. + * @return this builder + */ + @NonNull + public Builder setNetworkSpecifier(@Nullable final NetworkSpecifier specifier) { + mCaps.setNetworkSpecifier(specifier); + return this; + } + + /** + * Sets the optional transport specific information. + * + * @param info A concrete, parcelable framework class that extends {@link TransportInfo}, + * or null to clear it. + * @return this builder + */ + @NonNull + public Builder setTransportInfo(@Nullable final TransportInfo info) { + mCaps.setTransportInfo(info); + return this; + } + + /** + * Sets the signal strength. This is a signed integer, with higher values indicating a + * stronger signal. The exact units are bearer-dependent. For example, Wi-Fi uses the + * same RSSI units reported by wifi code. + *

+ * Note that when used to register a network callback, this specifies the minimum + * acceptable signal strength. When received as the state of an existing network it + * specifies the current value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means + * no value when received and has no effect when requesting a callback. + * + * Note: for security the system will throw if it receives a NetworkRequest where + * the underlying NetworkCapabilities has this member set from a source that does + * not hold the {@link android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP} + * permission. Apps with this permission can use this indirectly through + * {@link android.net.NetworkRequest}. + * + * @param signalStrength the bearer-specific signal strength. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) + public Builder setSignalStrength(final int signalStrength) { + mCaps.setSignalStrength(signalStrength); + return this; + } + + /** + * Sets the SSID of this network. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source, like an app using reflection to set this. + * + * @param ssid the SSID, or null to clear it. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setSsid(@Nullable final String ssid) { + mCaps.setSSID(ssid); + return this; + } + + /** + * Set the uid of the app causing this network to exist. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source. + * + * @param uid UID of the app. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setRequestorUid(final int uid) { + mCaps.setRequestorUid(uid); + return this; + } + + /** + * Set the package name of the app causing this network to exist. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source. + * + * @param packageName package name of the app, or null to clear it. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setRequestorPackageName(@Nullable final String packageName) { + mCaps.setRequestorPackageName(packageName); + return this; + } + + /** + * Builds the instance of the capabilities. + * + * @return the built instance of NetworkCapabilities. + */ + @NonNull + public NetworkCapabilities build() { + if (mCaps.getOwnerUid() != Process.INVALID_UID) { + if (!ArrayUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) { + throw new IllegalStateException("The owner UID must be included in " + + " administrator UIDs."); + } + } + return new NetworkCapabilities(mCaps); + } + } +} diff --git a/framework/src/android/net/NetworkConfig.java b/framework/src/android/net/NetworkConfig.java new file mode 100644 index 0000000000..32a2cda003 --- /dev/null +++ b/framework/src/android/net/NetworkConfig.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010 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 java.util.Locale; + +/** + * Describes the buildtime configuration of a network. + * Holds settings read from resources. + * @hide + */ +public class NetworkConfig { + /** + * Human readable string + */ + public String name; + + /** + * Type from ConnectivityManager + */ + public int type; + + /** + * the radio number from radio attributes config + */ + public int radio; + + /** + * higher number == higher priority when turning off connections + */ + public int priority; + + /** + * indicates the boot time dependencyMet setting + */ + public boolean dependencyMet; + + /** + * indicates the default restoral timer in seconds + * if the network is used as a special network feature + * -1 indicates no restoration of default + */ + public int restoreTime; + + /** + * input string from config.xml resource. Uses the form: + * [Connection name],[ConnectivityManager connection type], + * [associated radio-type],[priority],[dependencyMet] + */ + public NetworkConfig(String init) { + String fragments[] = init.split(","); + name = fragments[0].trim().toLowerCase(Locale.ROOT); + type = Integer.parseInt(fragments[1]); + radio = Integer.parseInt(fragments[2]); + priority = Integer.parseInt(fragments[3]); + restoreTime = Integer.parseInt(fragments[4]); + dependencyMet = Boolean.parseBoolean(fragments[5]); + } + + /** + * Indicates if this network is supposed to be default-routable + */ + public boolean isDefault() { + return (type == radio); + } +} diff --git a/framework/src/android/net/NetworkInfo.aidl b/framework/src/android/net/NetworkInfo.aidl new file mode 100644 index 0000000000..f501873029 --- /dev/null +++ b/framework/src/android/net/NetworkInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2007, 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; + +parcelable NetworkInfo; diff --git a/framework/src/android/net/NetworkInfo.java b/framework/src/android/net/NetworkInfo.java new file mode 100644 index 0000000000..d752901e2e --- /dev/null +++ b/framework/src/android/net/NetworkInfo.java @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2008 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Annotation.NetworkType; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.EnumMap; + +/** + * Describes the status of a network interface. + *

Use {@link ConnectivityManager#getActiveNetworkInfo()} to get an instance that represents + * the current network connection. + * + * @deprecated Callers should instead use the {@link ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes, or switch to use + * {@link ConnectivityManager#getNetworkCapabilities} or + * {@link ConnectivityManager#getLinkProperties} to get information synchronously. Keep + * in mind that while callbacks are guaranteed to be called for every event in order, + * synchronous calls have no such constraints, and as such it is unadvisable to use the + * synchronous methods inside the callbacks as they will often not offer a view of + * networking that is consistent (that is: they may return a past or a future state with + * respect to the event being processed by the callback). Instead, callers are advised + * to only use the arguments of the callbacks, possibly memorizing the specific bits of + * information they need to keep from one callback to another. + */ +@Deprecated +public class NetworkInfo implements Parcelable { + + /** + * Coarse-grained network state. This is probably what most applications should + * use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}. + * The mapping between the two is as follows: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Detailed stateCoarse-grained state
IDLEDISCONNECTED
SCANNINGDISCONNECTED
CONNECTINGCONNECTING
AUTHENTICATINGCONNECTING
OBTAINING_IPADDRCONNECTING
VERIFYING_POOR_LINKCONNECTING
CAPTIVE_PORTAL_CHECKCONNECTING
CONNECTEDCONNECTED
SUSPENDEDSUSPENDED
DISCONNECTINGDISCONNECTING
DISCONNECTEDDISCONNECTED
FAILEDDISCONNECTED
BLOCKEDDISCONNECTED
+ * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public enum State { + CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN + } + + /** + * The fine-grained state of a network connection. This level of detail + * is probably of interest to few applications. Most should use + * {@link android.net.NetworkInfo.State State} instead. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public enum DetailedState { + /** Ready to start data connection setup. */ + IDLE, + /** Searching for an available access point. */ + SCANNING, + /** Currently setting up data connection. */ + CONNECTING, + /** Network link established, performing authentication. */ + AUTHENTICATING, + /** Awaiting response from DHCP server in order to assign IP address information. */ + OBTAINING_IPADDR, + /** IP traffic should be available. */ + CONNECTED, + /** IP traffic is suspended */ + SUSPENDED, + /** Currently tearing down data connection. */ + DISCONNECTING, + /** IP traffic not available. */ + DISCONNECTED, + /** Attempt to connect failed. */ + FAILED, + /** Access to this network is blocked. */ + BLOCKED, + /** Link has poor connectivity. */ + VERIFYING_POOR_LINK, + /** Checking if network is a captive portal */ + CAPTIVE_PORTAL_CHECK + } + + /** + * This is the map described in the Javadoc comment above. The positions + * of the elements of the array must correspond to the ordinal values + * of DetailedState. + */ + private static final EnumMap stateMap = + new EnumMap(DetailedState.class); + + static { + stateMap.put(DetailedState.IDLE, State.DISCONNECTED); + stateMap.put(DetailedState.SCANNING, State.DISCONNECTED); + stateMap.put(DetailedState.CONNECTING, State.CONNECTING); + stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING); + stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING); + stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING); + stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING); + stateMap.put(DetailedState.CONNECTED, State.CONNECTED); + stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED); + stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING); + stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED); + stateMap.put(DetailedState.FAILED, State.DISCONNECTED); + stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED); + } + + private int mNetworkType; + private int mSubtype; + private String mTypeName; + private String mSubtypeName; + @NonNull + private State mState; + @NonNull + private DetailedState mDetailedState; + private String mReason; + private String mExtraInfo; + private boolean mIsFailover; + private boolean mIsAvailable; + private boolean mIsRoaming; + + /** + * Create a new instance of NetworkInfo. + * + * This may be useful for apps to write unit tests. + * + * @param type the legacy type of the network, as one of the ConnectivityManager.TYPE_* + * constants. + * @param subtype the subtype if applicable, as one of the TelephonyManager.NETWORK_TYPE_* + * constants. + * @param typeName a human-readable string for the network type, or an empty string or null. + * @param subtypeName a human-readable string for the subtype, or an empty string or null. + */ + public NetworkInfo(int type, @NetworkType int subtype, + @Nullable String typeName, @Nullable String subtypeName) { + if (!ConnectivityManager.isNetworkTypeValid(type) + && type != ConnectivityManager.TYPE_NONE) { + throw new IllegalArgumentException("Invalid network type: " + type); + } + mNetworkType = type; + mSubtype = subtype; + mTypeName = typeName; + mSubtypeName = subtypeName; + setDetailedState(DetailedState.IDLE, null, null); + mState = State.UNKNOWN; + } + + /** {@hide} */ + @UnsupportedAppUsage + public NetworkInfo(NetworkInfo source) { + if (source != null) { + synchronized (source) { + mNetworkType = source.mNetworkType; + mSubtype = source.mSubtype; + mTypeName = source.mTypeName; + mSubtypeName = source.mSubtypeName; + mState = source.mState; + mDetailedState = source.mDetailedState; + mReason = source.mReason; + mExtraInfo = source.mExtraInfo; + mIsFailover = source.mIsFailover; + mIsAvailable = source.mIsAvailable; + mIsRoaming = source.mIsRoaming; + } + } + } + + /** + * Reports the type of network to which the + * info in this {@code NetworkInfo} pertains. + * @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link + * ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link + * ConnectivityManager#TYPE_ETHERNET}, {@link ConnectivityManager#TYPE_BLUETOOTH}, or other + * types defined by {@link ConnectivityManager}. + * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport} + * instead with one of the NetworkCapabilities#TRANSPORT_* constants : + * {@link #getType} and {@link #getTypeName} cannot account for networks using + * multiple transports. Note that generally apps should not care about transport; + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and + * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that + * apps concerned with meteredness or bandwidth should be looking at, as they + * offer this information with much better accuracy. + */ + @Deprecated + public int getType() { + synchronized (this) { + return mNetworkType; + } + } + + /** + * @deprecated Use {@link NetworkCapabilities} instead + * @hide + */ + @Deprecated + public void setType(int type) { + synchronized (this) { + mNetworkType = type; + } + } + + /** + * Return a network-type-specific integer describing the subtype + * of the network. + * @return the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. + */ + @Deprecated + public int getSubtype() { + synchronized (this) { + return mSubtype; + } + } + + /** + * @hide + */ + @UnsupportedAppUsage + public void setSubtype(int subtype, String subtypeName) { + synchronized (this) { + mSubtype = subtype; + mSubtypeName = subtypeName; + } + } + + /** + * Return a human-readable name describe the type of the network, + * for example "WIFI" or "MOBILE". + * @return the name of the network type + * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport} + * instead with one of the NetworkCapabilities#TRANSPORT_* constants : + * {@link #getType} and {@link #getTypeName} cannot account for networks using + * multiple transports. Note that generally apps should not care about transport; + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and + * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that + * apps concerned with meteredness or bandwidth should be looking at, as they + * offer this information with much better accuracy. + */ + @Deprecated + public String getTypeName() { + synchronized (this) { + return mTypeName; + } + } + + /** + * Return a human-readable name describing the subtype of the network. + * @return the name of the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. + */ + @Deprecated + public String getSubtypeName() { + synchronized (this) { + return mSubtypeName; + } + } + + /** + * Indicates whether network connectivity exists or is in the process + * of being established. This is good for applications that need to + * do anything related to the network other than read or write data. + * For the latter, call {@link #isConnected()} instead, which guarantees + * that the network is fully usable. + * @return {@code true} if network connectivity exists or is in the process + * of being established, {@code false} otherwise. + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isConnectedOrConnecting() { + synchronized (this) { + return mState == State.CONNECTED || mState == State.CONNECTING; + } + } + + /** + * Indicates whether network connectivity exists and it is possible to establish + * connections and pass data. + *

Always call this before attempting to perform data transactions. + * @return {@code true} if network connectivity exists, {@code false} otherwise. + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isConnected() { + synchronized (this) { + return mState == State.CONNECTED; + } + } + + /** + * Indicates whether network connectivity is possible. A network is unavailable + * when a persistent or semi-persistent condition prevents the possibility + * of connecting to that network. Examples include + *

    + *
  • The device is out of the coverage area for any network of this type.
  • + *
  • The device is on a network other than the home network (i.e., roaming), and + * data roaming has been disabled.
  • + *
  • The device's radio is turned off, e.g., because airplane mode is enabled.
  • + *
+ * Since Android L, this always returns {@code true}, because the system only + * returns info for available networks. + * @return {@code true} if the network is available, {@code false} otherwise + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isAvailable() { + synchronized (this) { + return mIsAvailable; + } + } + + /** + * Sets if the network is available, ie, if the connectivity is possible. + * @param isAvailable the new availability value. + * @deprecated Use {@link NetworkCapabilities} instead + * + * @hide + */ + @Deprecated + @UnsupportedAppUsage + public void setIsAvailable(boolean isAvailable) { + synchronized (this) { + mIsAvailable = isAvailable; + } + } + + /** + * Indicates whether the current attempt to connect to the network + * resulted from the ConnectivityManager trying to fail over to this + * network following a disconnect from another network. + * @return {@code true} if this is a failover attempt, {@code false} + * otherwise. + * @deprecated This field is not populated in recent Android releases, + * and does not make a lot of sense in a multi-network world. + */ + @Deprecated + public boolean isFailover() { + synchronized (this) { + return mIsFailover; + } + } + + /** + * Set the failover boolean. + * @param isFailover {@code true} to mark the current connection attempt + * as a failover. + * @deprecated This hasn't been set in any recent Android release. + * @hide + */ + @Deprecated + @UnsupportedAppUsage + public void setFailover(boolean isFailover) { + synchronized (this) { + mIsFailover = isFailover; + } + } + + /** + * Indicates whether the device is currently roaming on this network. When + * {@code true}, it suggests that use of data on this network may incur + * extra costs. + * + * @return {@code true} if roaming is in effect, {@code false} otherwise. + * @deprecated Callers should switch to checking + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} + * instead, since that handles more complex situations, such as + * VPNs. + */ + @Deprecated + public boolean isRoaming() { + synchronized (this) { + return mIsRoaming; + } + } + + /** + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} instead. + * {@hide} + */ + @VisibleForTesting + @Deprecated + @UnsupportedAppUsage + public void setRoaming(boolean isRoaming) { + synchronized (this) { + mIsRoaming = isRoaming; + } + } + + /** + * Reports the current coarse-grained state of the network. + * @return the coarse-grained state + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public State getState() { + synchronized (this) { + return mState; + } + } + + /** + * Reports the current fine-grained state of the network. + * @return the fine-grained state + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public @NonNull DetailedState getDetailedState() { + synchronized (this) { + return mDetailedState; + } + } + + /** + * Sets the fine-grained state of the network. + * + * This is only useful for testing. + * + * @param detailedState the {@link DetailedState}. + * @param reason a {@code String} indicating the reason for the state change, + * if one was supplied. May be {@code null}. + * @param extraInfo an optional {@code String} providing addditional network state + * information passed up from the lower networking layers. + * @deprecated Use {@link NetworkCapabilities} instead. + */ + @Deprecated + public void setDetailedState(@NonNull DetailedState detailedState, @Nullable String reason, + @Nullable String extraInfo) { + synchronized (this) { + this.mDetailedState = detailedState; + this.mState = stateMap.get(detailedState); + this.mReason = reason; + this.mExtraInfo = extraInfo; + } + } + + /** + * Set the extraInfo field. + * @param extraInfo an optional {@code String} providing addditional network state + * information passed up from the lower networking layers. + * @deprecated See {@link NetworkInfo#getExtraInfo}. + * @hide + */ + @Deprecated + public void setExtraInfo(String extraInfo) { + synchronized (this) { + this.mExtraInfo = extraInfo; + } + } + + /** + * Report the reason an attempt to establish connectivity failed, + * if one is available. + * @return the reason for failure, or null if not available + * @deprecated This method does not have a consistent contract that could make it useful + * to callers. + */ + public String getReason() { + synchronized (this) { + return mReason; + } + } + + /** + * Report the extra information about the network state, if any was + * provided by the lower networking layers. + * @return the extra information, or null if not available + * @deprecated Use other services e.g. WifiManager to get additional information passed up from + * the lower networking layers. + */ + @Deprecated + public String getExtraInfo() { + synchronized (this) { + return mExtraInfo; + } + } + + @Override + public String toString() { + synchronized (this) { + final StringBuilder builder = new StringBuilder("["); + builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()). + append("], state: ").append(mState).append("/").append(mDetailedState). + append(", reason: ").append(mReason == null ? "(unspecified)" : mReason). + append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo). + append(", failover: ").append(mIsFailover). + append(", available: ").append(mIsAvailable). + append(", roaming: ").append(mIsRoaming). + append("]"); + return builder.toString(); + } + } + + /** + * Returns a brief summary string suitable for debugging. + * @hide + */ + public String toShortString() { + synchronized (this) { + final StringBuilder builder = new StringBuilder(); + builder.append(getTypeName()); + + final String subtype = getSubtypeName(); + if (!TextUtils.isEmpty(subtype)) { + builder.append("[").append(subtype).append("]"); + } + + builder.append(" "); + builder.append(mDetailedState); + if (mIsRoaming) { + builder.append(" ROAMING"); + } + if (mExtraInfo != null) { + builder.append(" extra: ").append(mExtraInfo); + } + return builder.toString(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + synchronized (this) { + dest.writeInt(mNetworkType); + dest.writeInt(mSubtype); + dest.writeString(mTypeName); + dest.writeString(mSubtypeName); + dest.writeString(mState.name()); + dest.writeString(mDetailedState.name()); + dest.writeInt(mIsFailover ? 1 : 0); + dest.writeInt(mIsAvailable ? 1 : 0); + dest.writeInt(mIsRoaming ? 1 : 0); + dest.writeString(mReason); + dest.writeString(mExtraInfo); + } + } + + public static final @android.annotation.NonNull Creator CREATOR = new Creator() { + @Override + public NetworkInfo createFromParcel(Parcel in) { + int netType = in.readInt(); + int subtype = in.readInt(); + String typeName = in.readString(); + String subtypeName = in.readString(); + NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName); + netInfo.mState = State.valueOf(in.readString()); + netInfo.mDetailedState = DetailedState.valueOf(in.readString()); + netInfo.mIsFailover = in.readInt() != 0; + netInfo.mIsAvailable = in.readInt() != 0; + netInfo.mIsRoaming = in.readInt() != 0; + netInfo.mReason = in.readString(); + netInfo.mExtraInfo = in.readString(); + return netInfo; + } + + @Override + public NetworkInfo[] newArray(int size) { + return new NetworkInfo[size]; + } + }; +} diff --git a/framework/src/android/net/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java new file mode 100644 index 0000000000..14cb51c85d --- /dev/null +++ b/framework/src/android/net/NetworkProvider.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 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 android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.util.Log; + +/** + * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device + * to networks and makes them available to the core network stack by creating + * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted + * with via networking APIs such as {@link ConnectivityManager}. + * + * Subclasses should implement {@link #onNetworkRequested} and {@link #onNetworkRequestWithdrawn} + * to receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the + * best (highest-scoring) network for any request is generally not used by the system, and torn + * down. + * + * @hide + */ +@SystemApi +public class NetworkProvider { + /** + * {@code providerId} value that indicates the absence of a provider. It is the providerId of + * any NetworkProvider that is not currently registered, and of any NetworkRequest that is not + * currently being satisfied by a network. + */ + public static final int ID_NONE = -1; + + /** + * The first providerId value that will be allocated. + * @hide only used by ConnectivityService. + */ + public static final int FIRST_PROVIDER_ID = 1; + + /** @hide only used by ConnectivityService */ + public static final int CMD_REQUEST_NETWORK = 1; + /** @hide only used by ConnectivityService */ + public static final int CMD_CANCEL_REQUEST = 2; + + private final Messenger mMessenger; + private final String mName; + private final Context mContext; + + private int mProviderId = ID_NONE; + + /** + * Constructs a new NetworkProvider. + * + * @param looper the Looper on which to run {@link #onNetworkRequested} and + * {@link #onNetworkRequestWithdrawn}. + * @param name the name of the listener, used only for debugging. + * + * @hide + */ + @SystemApi + public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) { + Handler handler = new Handler(looper) { + @Override + public void handleMessage(Message m) { + switch (m.what) { + case CMD_REQUEST_NETWORK: + onNetworkRequested((NetworkRequest) m.obj, m.arg1, m.arg2); + break; + case CMD_CANCEL_REQUEST: + onNetworkRequestWithdrawn((NetworkRequest) m.obj); + break; + default: + Log.e(mName, "Unhandled message: " + m.what); + } + } + }; + mContext = context; + mMessenger = new Messenger(handler); + mName = name; + } + + // TODO: consider adding a register() method so ConnectivityManager does not need to call this. + /** @hide */ + public @Nullable Messenger getMessenger() { + return mMessenger; + } + + /** @hide */ + public @NonNull String getName() { + return mName; + } + + /** + * Returns the ID of this provider. This is known only once the provider is registered via + * {@link ConnectivityManager#registerNetworkProvider()}, otherwise the ID is {@link #ID_NONE}. + * This ID must be used when registering any {@link NetworkAgent}s. + */ + public int getProviderId() { + return mProviderId; + } + + /** @hide */ + public void setProviderId(int providerId) { + mProviderId = providerId; + } + + /** + * Called when a NetworkRequest is received. The request may be a new request or an existing + * request with a different score. + * + * @param request the NetworkRequest being received + * @param score the score of the network currently satisfying the request, or 0 if none. + * @param providerId the ID of the provider that created the network currently satisfying this + * request, or {@link #ID_NONE} if none. + * + * @hide + */ + @SystemApi + public void onNetworkRequested(@NonNull NetworkRequest request, + @IntRange(from = 0, to = 99) int score, int providerId) {} + + /** + * Called when a NetworkRequest is withdrawn. + * @hide + */ + @SystemApi + public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {} + + /** + * Asserts that no provider will ever be able to satisfy the specified request. The provider + * must only call this method if it knows that it is the only provider on the system capable of + * satisfying this request, and that the request cannot be satisfied. The application filing the + * request will receive an {@link NetworkCallback#onUnavailable()} callback. + * + * @param request the request that permanently cannot be fulfilled + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { + ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request); + } +} diff --git a/framework/src/android/net/NetworkRequest.aidl b/framework/src/android/net/NetworkRequest.aidl new file mode 100644 index 0000000000..508defc6b4 --- /dev/null +++ b/framework/src/android/net/NetworkRequest.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014, 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; + +parcelable NetworkRequest; + diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java new file mode 100644 index 0000000000..c4d1b09a5c --- /dev/null +++ b/framework/src/android/net/NetworkRequest.java @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2014 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.net.NetworkCapabilities.NetCapability; +import android.net.NetworkCapabilities.Transport; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.text.TextUtils; +import android.util.proto.ProtoOutputStream; + +import java.util.Objects; +import java.util.Set; + +/** + * Defines a request for a network, made through {@link NetworkRequest.Builder} and used + * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes + * via {@link ConnectivityManager#registerNetworkCallback}. + */ +public class NetworkRequest implements Parcelable { + /** + * The first requestId value that will be allocated. + * @hide only used by ConnectivityService. + */ + public static final int FIRST_REQUEST_ID = 1; + + /** + * The requestId value that represents the absence of a request. + * @hide only used by ConnectivityService. + */ + public static final int REQUEST_ID_NONE = -1; + + /** + * The {@link NetworkCapabilities} that define this request. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public final @NonNull NetworkCapabilities networkCapabilities; + + /** + * Identifies the request. NetworkRequests should only be constructed by + * the Framework and given out to applications as tokens to be used to identify + * the request. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public final int requestId; + + /** + * Set for legacy requests and the default. Set to TYPE_NONE for none. + * Causes CONNECTIVITY_ACTION broadcasts to be sent. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public final int legacyType; + + /** + * A NetworkRequest as used by the system can be one of the following types: + * + * - LISTEN, for which the framework will issue callbacks about any + * and all networks that match the specified NetworkCapabilities, + * + * - REQUEST, capable of causing a specific network to be created + * first (e.g. a telephony DUN request), the framework will issue + * callbacks about the single, highest scoring current network + * (if any) that matches the specified NetworkCapabilities, or + * + * - TRACK_DEFAULT, a hybrid of the two designed such that the + * framework will issue callbacks for the single, highest scoring + * current network (if any) that matches the capabilities of the + * default Internet request (mDefaultRequest), but which cannot cause + * the framework to either create or retain the existence of any + * specific network. Note that from the point of view of the request + * matching code, TRACK_DEFAULT is identical to REQUEST: its special + * behaviour is not due to different semantics, but to the fact that + * the system will only ever create a TRACK_DEFAULT with capabilities + * that are identical to the default request's capabilities, thus + * causing it to share fate in every way with the default request. + * + * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks + * to retain the NET_CAPABILITY_FOREGROUND capability. A network with + * no foreground requests is in the background. A network that has + * one or more background requests and loses its last foreground + * request to a higher-scoring network will not go into the + * background immediately, but will linger and go into the background + * after the linger timeout. + * + * - The value NONE is used only by applications. When an application + * creates a NetworkRequest, it does not have a type; the type is set + * by the system depending on the method used to file the request + * (requestNetwork, registerNetworkCallback, etc.). + * + * @hide + */ + public static enum Type { + NONE, + LISTEN, + TRACK_DEFAULT, + REQUEST, + BACKGROUND_REQUEST, + }; + + /** + * The type of the request. This is only used by the system and is always NONE elsewhere. + * + * @hide + */ + public final Type type; + + /** + * @hide + */ + public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId, Type type) { + if (nc == null) { + throw new NullPointerException(); + } + requestId = rId; + networkCapabilities = nc; + this.legacyType = legacyType; + this.type = type; + } + + /** + * @hide + */ + public NetworkRequest(NetworkRequest that) { + networkCapabilities = new NetworkCapabilities(that.networkCapabilities); + requestId = that.requestId; + this.legacyType = that.legacyType; + this.type = that.type; + } + + /** + * Builder used to create {@link NetworkRequest} objects. Specify the Network features + * needed in terms of {@link NetworkCapabilities} features + */ + public static class Builder { + private final NetworkCapabilities mNetworkCapabilities; + + /** + * Default constructor for Builder. + */ + public Builder() { + // By default, restrict this request to networks available to this app. + // Apps can rescind this restriction, but ConnectivityService will enforce + // it for apps that do not have the NETWORK_SETTINGS permission. + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.setSingleUid(Process.myUid()); + } + + /** + * Build {@link NetworkRequest} give the current set of capabilities. + */ + public NetworkRequest build() { + // Make a copy of mNetworkCapabilities so we don't inadvertently remove NOT_RESTRICTED + // when later an unrestricted capability could be added to mNetworkCapabilities, in + // which case NOT_RESTRICTED should be returned to mNetworkCapabilities, which + // maybeMarkCapabilitiesRestricted() doesn't add back. + final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); + nc.maybeMarkCapabilitiesRestricted(); + return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, + ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); + } + + /** + * Add the given capability requirement to this builder. These represent + * the requested network's required capabilities. Note that when searching + * for a network to satisfy a request, all capabilities requested must be + * satisfied. + * + * @param capability The capability to add. + * @return The builder to facilitate chaining + * {@code builder.addCapability(...).addCapability();}. + */ + public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.addCapability(capability); + return this; + } + + /** + * Removes (if found) the given capability from this builder instance. + * + * @param capability The capability to remove. + * @return The builder to facilitate chaining. + */ + public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.removeCapability(capability); + return this; + } + + /** + * Set the {@code NetworkCapabilities} for this builder instance, + * overriding any capabilities that had been previously set. + * + * @param nc The superseding {@code NetworkCapabilities} instance. + * @return The builder to facilitate chaining. + * @hide + */ + public Builder setCapabilities(NetworkCapabilities nc) { + mNetworkCapabilities.set(nc); + return this; + } + + /** + * Set the watched UIDs for this request. This will be reset and wiped out unless + * the calling app holds the CHANGE_NETWORK_STATE permission. + * + * @param uids The watched UIDs as a set of UidRanges, or null for everything. + * @return The builder to facilitate chaining. + * @hide + */ + public Builder setUids(Set uids) { + mNetworkCapabilities.setUids(uids); + return this; + } + + /** + * Add a capability that must not exist in the requested network. + *

+ * If the capability was previously added to the list of required capabilities (for + * example, it was there by default or added using {@link #addCapability(int)} method), then + * it will be removed from the list of required capabilities as well. + * + * @see #addCapability(int) + * + * @param capability The capability to add to unwanted capability list. + * @return The builder to facilitate chaining. + * + * @hide + */ + public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.addUnwantedCapability(capability); + return this; + } + + /** + * Completely clears all the {@code NetworkCapabilities} from this builder instance, + * removing even the capabilities that are set by default when the object is constructed. + * + * @return The builder to facilitate chaining. + */ + @NonNull + public Builder clearCapabilities() { + mNetworkCapabilities.clearAll(); + return this; + } + + /** + * Adds the given transport requirement to this builder. These represent + * the set of allowed transports for the request. Only networks using one + * of these transports will satisfy the request. If no particular transports + * are required, none should be specified here. + * + * @param transportType The transport type to add. + * @return The builder to facilitate chaining. + */ + public Builder addTransportType(@NetworkCapabilities.Transport int transportType) { + mNetworkCapabilities.addTransportType(transportType); + return this; + } + + /** + * Removes (if found) the given transport from this builder instance. + * + * @param transportType The transport type to remove. + * @return The builder to facilitate chaining. + */ + public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) { + mNetworkCapabilities.removeTransportType(transportType); + return this; + } + + /** + * @hide + */ + public Builder setLinkUpstreamBandwidthKbps(int upKbps) { + mNetworkCapabilities.setLinkUpstreamBandwidthKbps(upKbps); + return this; + } + /** + * @hide + */ + public Builder setLinkDownstreamBandwidthKbps(int downKbps) { + mNetworkCapabilities.setLinkDownstreamBandwidthKbps(downKbps); + return this; + } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * If the {@code networkSpecifier} is provided, it shall be interpreted as follows: + *
    + *
  • If the specifier can be parsed as an integer, it will be treated as a + * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be + * interpreted as a SubscriptionId. + *
  • If the value is an ethernet interface name, it will be treated as such. + *
  • For all other cases, the behavior is undefined. + *
+ * + * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular + * network request or an ethernet interface name in ethernet + * network request. + * + * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead. + */ + @Deprecated + public Builder setNetworkSpecifier(String networkSpecifier) { + try { + int subId = Integer.parseInt(networkSpecifier); + return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(subId).build()); + } catch (NumberFormatException nfe) { + // A StringNetworkSpecifier does not accept null or empty ("") strings. When network + // specifiers were strings a null string and an empty string were considered + // equivalent. Hence no meaning is attached to a null or empty ("") string. + return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null + : new StringNetworkSpecifier(networkSpecifier)); + } + } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * + * @param networkSpecifier A concrete, parcelable framework class that extends + * NetworkSpecifier. + */ + public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) { + if (networkSpecifier instanceof MatchAllNetworkSpecifier) { + throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted"); + } + mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); + return this; + } + + /** + * Sets the signal strength. This is a signed integer, with higher values indicating a + * stronger signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same + * RSSI units reported by WifiManager. + *

+ * Note that when used to register a network callback, this specifies the minimum acceptable + * signal strength. When received as the state of an existing network it specifies the + * current value. A value of {@code SIGNAL_STRENGTH_UNSPECIFIED} means no value when + * received and has no effect when requesting a callback. + * + *

This method requires the caller to hold the + * {@link android.Manifest.permission#NETWORK_SIGNAL_STRENGTH_WAKEUP} permission + * + * @param signalStrength the bearer-specific signal strength. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) + public @NonNull Builder setSignalStrength(int signalStrength) { + mNetworkCapabilities.setSignalStrength(signalStrength); + return this; + } + } + + // implement the Parcelable interface + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel dest, int flags) { + networkCapabilities.writeToParcel(dest, flags); + dest.writeInt(legacyType); + dest.writeInt(requestId); + dest.writeString(type.name()); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public NetworkRequest createFromParcel(Parcel in) { + NetworkCapabilities nc = NetworkCapabilities.CREATOR.createFromParcel(in); + int legacyType = in.readInt(); + int requestId = in.readInt(); + Type type = Type.valueOf(in.readString()); // IllegalArgumentException if invalid. + NetworkRequest result = new NetworkRequest(nc, legacyType, requestId, type); + return result; + } + public NetworkRequest[] newArray(int size) { + return new NetworkRequest[size]; + } + }; + + /** + * Returns true iff. this NetworkRequest is of type LISTEN. + * + * @hide + */ + public boolean isListen() { + return type == Type.LISTEN; + } + + /** + * Returns true iff. the contained NetworkRequest is one that: + * + * - should be associated with at most one satisfying network + * at a time; + * + * - should cause a network to be kept up, but not necessarily in + * the foreground, if it is the best network which can satisfy the + * NetworkRequest. + * + * For full detail of how isRequest() is used for pairing Networks with + * NetworkRequests read rematchNetworkAndRequests(). + * + * @hide + */ + public boolean isRequest() { + return isForegroundRequest() || isBackgroundRequest(); + } + + /** + * Returns true iff. the contained NetworkRequest is one that: + * + * - should be associated with at most one satisfying network + * at a time; + * + * - should cause a network to be kept up and in the foreground if + * it is the best network which can satisfy the NetworkRequest. + * + * For full detail of how isRequest() is used for pairing Networks with + * NetworkRequests read rematchNetworkAndRequests(). + * + * @hide + */ + public boolean isForegroundRequest() { + return type == Type.TRACK_DEFAULT || type == Type.REQUEST; + } + + /** + * Returns true iff. this NetworkRequest is of type BACKGROUND_REQUEST. + * + * @hide + */ + public boolean isBackgroundRequest() { + return type == Type.BACKGROUND_REQUEST; + } + + /** + * @see Builder#addCapability(int) + */ + public boolean hasCapability(@NetCapability int capability) { + return networkCapabilities.hasCapability(capability); + } + + /** + * @see Builder#addUnwantedCapability(int) + * + * @hide + */ + public boolean hasUnwantedCapability(@NetCapability int capability) { + return networkCapabilities.hasUnwantedCapability(capability); + } + + /** + * Returns true if and only if the capabilities requested in this NetworkRequest are satisfied + * by the provided {@link NetworkCapabilities}. + * + * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not + * satisfy any request. + */ + public boolean canBeSatisfiedBy(@Nullable NetworkCapabilities nc) { + return networkCapabilities.satisfiedByNetworkCapabilities(nc); + } + + /** + * @see Builder#addTransportType(int) + */ + public boolean hasTransport(@Transport int transportType) { + return networkCapabilities.hasTransport(transportType); + } + + /** + * @see Builder#setNetworkSpecifier(NetworkSpecifier) + */ + @Nullable + public NetworkSpecifier getNetworkSpecifier() { + return networkCapabilities.getNetworkSpecifier(); + } + + /** + * @return the uid of the app making the request. + * + * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest} object was + * not obtained from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + public int getRequestorUid() { + return networkCapabilities.getRequestorUid(); + } + + /** + * @return the package name of the app making the request. + * + * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained + * from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + @Nullable + public String getRequestorPackageName() { + return networkCapabilities.getRequestorPackageName(); + } + + public String toString() { + return "NetworkRequest [ " + type + " id=" + requestId + + (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") + + ", " + networkCapabilities.toString() + " ]"; + } + + private int typeToProtoEnum(Type t) { + switch (t) { + case NONE: + return NetworkRequestProto.TYPE_NONE; + case LISTEN: + return NetworkRequestProto.TYPE_LISTEN; + case TRACK_DEFAULT: + return NetworkRequestProto.TYPE_TRACK_DEFAULT; + case REQUEST: + return NetworkRequestProto.TYPE_REQUEST; + case BACKGROUND_REQUEST: + return NetworkRequestProto.TYPE_BACKGROUND_REQUEST; + default: + return NetworkRequestProto.TYPE_UNKNOWN; + } + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type)); + proto.write(NetworkRequestProto.REQUEST_ID, requestId); + proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType); + networkCapabilities.dumpDebug(proto, NetworkRequestProto.NETWORK_CAPABILITIES); + + proto.end(token); + } + + public boolean equals(@Nullable Object obj) { + if (obj instanceof NetworkRequest == false) return false; + NetworkRequest that = (NetworkRequest)obj; + return (that.legacyType == this.legacyType && + that.requestId == this.requestId && + that.type == this.type && + Objects.equals(that.networkCapabilities, this.networkCapabilities)); + } + + public int hashCode() { + return Objects.hash(requestId, legacyType, networkCapabilities, type); + } +} diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java new file mode 100644 index 0000000000..8be4af7b13 --- /dev/null +++ b/framework/src/android/net/NetworkUtils.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2008 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 android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.system.ErrnoException; +import android.util.Log; +import android.util.Pair; + +import com.android.net.module.util.Inet4AddressUtils; + +import java.io.FileDescriptor; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Locale; +import java.util.TreeSet; + +/** + * Native methods for managing network interfaces. + * + * {@hide} + */ +public class NetworkUtils { + + private static final String TAG = "NetworkUtils"; + + /** + * Attaches a socket filter that drops all of incoming packets. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void attachDropAllBPFFilter(FileDescriptor fd) throws SocketException; + + /** + * Detaches a socket filter. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void detachBPFFilter(FileDescriptor fd) throws SocketException; + + /** + * Binds the current process to the network designated by {@code netId}. All sockets created + * in the future (and not explicitly bound via a bound {@link SocketFactory} (see + * {@link Network#getSocketFactory}) will be bound to this network. Note that if this + * {@code Network} ever disconnects all sockets created in this way will cease to work. This + * is by design so an application doesn't accidentally use sockets it thinks are still bound to + * a particular {@code Network}. Passing NETID_UNSET clears the binding. + */ + public native static boolean bindProcessToNetwork(int netId); + + /** + * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if + * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}. + */ + public native static int getBoundNetworkForProcess(); + + /** + * Binds host resolutions performed by this process to the network designated by {@code netId}. + * {@link #bindProcessToNetwork} takes precedence over this setting. Passing NETID_UNSET clears + * the binding. + * + * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature(). + */ + @Deprecated + public native static boolean bindProcessToNetworkForHostResolution(int netId); + + /** + * Explicitly binds {@code fd} to the network designated by {@code netId}. This + * overrides any binding via {@link #bindProcessToNetwork}. + * @return 0 on success or negative errno on failure. + */ + public static native int bindSocketToNetwork(FileDescriptor fd, int netId); + + /** + * Protect {@code fd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static native boolean protectFromVpn(FileDescriptor fd); + + /** + * Protect {@code socketfd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public native static boolean protectFromVpn(int socketfd); + + /** + * Determine if {@code uid} can access network designated by {@code netId}. + * @return {@code true} if {@code uid} can access network, {@code false} otherwise. + */ + public native static boolean queryUserAccess(int uid, int netId); + + /** + * DNS resolver series jni method. + * Issue the query {@code msg} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkSend( + int netId, byte[] msg, int msglen, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated + * with Domain Name {@code dname} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkQuery( + int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Read a result for the query associated with the {@code fd}. + * @return DnsResponse containing blob answer and rcode + */ + public static native DnsResolver.DnsResponse resNetworkResult(FileDescriptor fd) + throws ErrnoException; + + /** + * DNS resolver series jni method. + * Attempts to cancel the in-progress query associated with the {@code fd}. + */ + public static native void resNetworkCancel(FileDescriptor fd); + + /** + * DNS resolver series jni method. + * Attempts to get network which resolver will use if no network is explicitly selected. + */ + public static native Network getDnsNetwork() throws ErrnoException; + + /** + * Get the tcp repair window associated with the {@code fd}. + * + * @param fd the tcp socket's {@link FileDescriptor}. + * @return a {@link TcpRepairWindow} object indicates tcp window size. + */ + public static native TcpRepairWindow getTcpRepairWindow(FileDescriptor fd) + throws ErrnoException; + + /** + * @see Inet4AddressUtils#intToInet4AddressHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)} + * or {@link Inet4AddressUtils#intToInet4AddressHTL(int)} + */ + @Deprecated + @UnsupportedAppUsage + public static InetAddress intToInetAddress(int hostAddress) { + return Inet4AddressUtils.intToInet4AddressHTL(hostAddress); + } + + /** + * @see Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address) + * @deprecated Use either {@link Inet4AddressUtils#inet4AddressToIntHTH(Inet4Address)} + * or {@link Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)} + */ + @Deprecated + public static int inetAddressToInt(Inet4Address inetAddr) + throws IllegalArgumentException { + return Inet4AddressUtils.inet4AddressToIntHTL(inetAddr); + } + + /** + * @see Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTH(int)} + * or {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)} + */ + @Deprecated + @UnsupportedAppUsage + public static int prefixLengthToNetmaskInt(int prefixLength) + throws IllegalArgumentException { + return Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL(prefixLength); + } + + /** + * Convert a IPv4 netmask integer to a prefix length + * @param netmask as an integer (0xff000000 for a /8 subnet) + * @return the network prefix length + */ + public static int netmaskIntToPrefixLength(int netmask) { + return Integer.bitCount(netmask); + } + + /** + * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous. + * @param netmask as a {@code Inet4Address}. + * @return the network prefix length + * @throws IllegalArgumentException the specified netmask was not contiguous. + * @hide + * @deprecated use {@link Inet4AddressUtils#netmaskToPrefixLength(Inet4Address)} + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public static int netmaskToPrefixLength(Inet4Address netmask) { + // This is only here because some apps seem to be using it (@UnsupportedAppUsage). + return Inet4AddressUtils.netmaskToPrefixLength(netmask); + } + + + /** + * Create an InetAddress from a string where the string must be a standard + * representation of a V4 or V6 address. Avoids doing a DNS lookup on failure + * but it will throw an IllegalArgumentException in that case. + * @param addrString + * @return the InetAddress + * @hide + * @deprecated Use {@link InetAddresses#parseNumericAddress(String)}, if possible. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @Deprecated + public static InetAddress numericToInetAddress(String addrString) + throws IllegalArgumentException { + return InetAddress.parseNumericAddress(addrString); + } + + /** + * Masks a raw IP address byte array with the specified prefix length. + */ + public static void maskRawAddress(byte[] array, int prefixLength) { + if (prefixLength < 0 || prefixLength > array.length * 8) { + throw new RuntimeException("IP address with " + array.length + + " bytes has invalid prefix length " + prefixLength); + } + + int offset = prefixLength / 8; + int remainder = prefixLength % 8; + byte mask = (byte)(0xFF << (8 - remainder)); + + if (offset < array.length) array[offset] = (byte)(array[offset] & mask); + + offset++; + + for (; offset < array.length; offset++) { + array[offset] = 0; + } + } + + /** + * Get InetAddress masked with prefixLength. Will never return null. + * @param address the IP address to mask with + * @param prefixLength the prefixLength used to mask the IP + */ + public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { + byte[] array = address.getAddress(); + maskRawAddress(array, prefixLength); + + InetAddress netPart = null; + try { + netPart = InetAddress.getByAddress(array); + } catch (UnknownHostException e) { + throw new RuntimeException("getNetworkPart error - " + e.toString()); + } + return netPart; + } + + /** + * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static int getImplicitNetmask(Inet4Address address) { + // Only here because it seems to be used by apps + return Inet4AddressUtils.getImplicitNetmask(address); + } + + /** + * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64". + * @hide + */ + public static Pair parseIpAndMask(String ipAndMaskString) { + InetAddress address = null; + int prefixLength = -1; + try { + String[] pieces = ipAndMaskString.split("/", 2); + prefixLength = Integer.parseInt(pieces[1]); + address = InetAddress.parseNumericAddress(pieces[0]); + } catch (NullPointerException e) { // Null string. + } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. + } catch (NumberFormatException e) { // Non-numeric prefix. + } catch (IllegalArgumentException e) { // Invalid IP address. + } + + if (address == null || prefixLength == -1) { + throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString); + } + + return new Pair(address, prefixLength); + } + + /** + * Convert a 32 char hex string into a Inet6Address. + * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be + * made into an Inet6Address + * @param addrHexString a 32 character hex string representing an IPv6 addr + * @return addr an InetAddress representation for the string + */ + public static InetAddress hexToInet6Address(String addrHexString) + throws IllegalArgumentException { + try { + return numericToInetAddress(String.format(Locale.US, "%s:%s:%s:%s:%s:%s:%s:%s", + addrHexString.substring(0,4), addrHexString.substring(4,8), + addrHexString.substring(8,12), addrHexString.substring(12,16), + addrHexString.substring(16,20), addrHexString.substring(20,24), + addrHexString.substring(24,28), addrHexString.substring(28,32))); + } catch (Exception e) { + Log.e("NetworkUtils", "error in hexToInet6Address(" + addrHexString + "): " + e); + throw new IllegalArgumentException(e); + } + } + + /** + * Trim leading zeros from IPv4 address strings + * Our base libraries will interpret that as octel.. + * Must leave non v4 addresses and host names alone. + * For example, 192.168.000.010 -> 192.168.0.10 + * TODO - fix base libraries and remove this function + * @param addr a string representing an ip addr + * @return a string propertly trimmed + */ + @UnsupportedAppUsage + public static String trimV4AddrZeros(String addr) { + if (addr == null) return null; + String[] octets = addr.split("\\."); + if (octets.length != 4) return addr; + StringBuilder builder = new StringBuilder(16); + String result = null; + for (int i = 0; i < 4; i++) { + try { + if (octets[i].length() > 3) return addr; + builder.append(Integer.parseInt(octets[i])); + } catch (NumberFormatException e) { + return addr; + } + if (i < 3) builder.append('.'); + } + result = builder.toString(); + return result; + } + + /** + * Returns a prefix set without overlaps. + * + * This expects the src set to be sorted from shorter to longer. Results are undefined + * failing this condition. The returned prefix set is sorted in the same order as the + * passed set, with the same comparator. + */ + private static TreeSet deduplicatePrefixSet(final TreeSet src) { + final TreeSet dst = new TreeSet<>(src.comparator()); + // Prefixes match addresses that share their upper part up to their length, therefore + // the only kind of possible overlap in two prefixes is strict inclusion of the longer + // (more restrictive) in the shorter (including equivalence if they have the same + // length). + // Because prefixes in the src set are sorted from shorter to longer, deduplicating + // is done by simply iterating in order, and not adding any longer prefix that is + // already covered by a shorter one. + newPrefixes: + for (IpPrefix newPrefix : src) { + for (IpPrefix existingPrefix : dst) { + if (existingPrefix.containsPrefix(newPrefix)) { + continue newPrefixes; + } + } + dst.add(newPrefix); + } + return dst; + } + + /** + * Returns how many IPv4 addresses match any of the prefixes in the passed ordered set. + * + * Obviously this returns an integral value between 0 and 2**32. + * The behavior is undefined if any of the prefixes is not an IPv4 prefix or if the + * set is not ordered smallest prefix to longer prefix. + * + * @param prefixes the set of prefixes, ordered by length + */ + public static long routedIPv4AddressCount(final TreeSet prefixes) { + long routedIPCount = 0; + for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) { + if (!prefix.isIPv4()) { + Log.wtf(TAG, "Non-IPv4 prefix in routedIPv4AddressCount"); + } + int rank = 32 - prefix.getPrefixLength(); + routedIPCount += 1L << rank; + } + return routedIPCount; + } + + /** + * Returns how many IPv6 addresses match any of the prefixes in the passed ordered set. + * + * This returns a BigInteger between 0 and 2**128. + * The behavior is undefined if any of the prefixes is not an IPv6 prefix or if the + * set is not ordered smallest prefix to longer prefix. + */ + public static BigInteger routedIPv6AddressCount(final TreeSet prefixes) { + BigInteger routedIPCount = BigInteger.ZERO; + for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) { + if (!prefix.isIPv6()) { + Log.wtf(TAG, "Non-IPv6 prefix in routedIPv6AddressCount"); + } + int rank = 128 - prefix.getPrefixLength(); + routedIPCount = routedIPCount.add(BigInteger.ONE.shiftLeft(rank)); + } + return routedIPCount; + } + +} diff --git a/framework/src/android/net/PacProxySelector.java b/framework/src/android/net/PacProxySelector.java new file mode 100644 index 0000000000..326943a27d --- /dev/null +++ b/framework/src/android/net/PacProxySelector.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013 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 android.os.ServiceManager; +import android.util.Log; + +import com.android.net.IProxyService; + +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +/** + * @hide + */ +public class PacProxySelector extends ProxySelector { + private static final String TAG = "PacProxySelector"; + public static final String PROXY_SERVICE = "com.android.net.IProxyService"; + private static final String SOCKS = "SOCKS "; + private static final String PROXY = "PROXY "; + + private IProxyService mProxyService; + private final List mDefaultList; + + public PacProxySelector() { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + if (mProxyService == null) { + // Added because of b10267814 where mako is restarting. + Log.e(TAG, "PacProxyInstaller: no proxy service"); + } + mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY); + } + + @Override + public List select(URI uri) { + if (mProxyService == null) { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + } + if (mProxyService == null) { + Log.e(TAG, "select: no proxy service return NO_PROXY"); + return Lists.newArrayList(java.net.Proxy.NO_PROXY); + } + String response = null; + String urlString; + try { + // Strip path and username/password from URI so it's not visible to PAC script. The + // path often contains credentials the app does not want exposed to a potentially + // malicious PAC script. + if (!"http".equalsIgnoreCase(uri.getScheme())) { + uri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), "/", null, null); + } + urlString = uri.toURL().toString(); + } catch (URISyntaxException e) { + urlString = uri.getHost(); + } catch (MalformedURLException e) { + urlString = uri.getHost(); + } + try { + response = mProxyService.resolvePacFile(uri.getHost(), urlString); + } catch (Exception e) { + Log.e(TAG, "Error resolving PAC File", e); + } + if (response == null) { + return mDefaultList; + } + + return parseResponse(response); + } + + private static List parseResponse(String response) { + String[] split = response.split(";"); + List ret = Lists.newArrayList(); + for (String s : split) { + String trimmed = s.trim(); + if (trimmed.equals("DIRECT")) { + ret.add(java.net.Proxy.NO_PROXY); + } else if (trimmed.startsWith(PROXY)) { + Proxy proxy = proxyFromHostPort(Type.HTTP, trimmed.substring(PROXY.length())); + if (proxy != null) { + ret.add(proxy); + } + } else if (trimmed.startsWith(SOCKS)) { + Proxy proxy = proxyFromHostPort(Type.SOCKS, trimmed.substring(SOCKS.length())); + if (proxy != null) { + ret.add(proxy); + } + } + } + if (ret.size() == 0) { + ret.add(java.net.Proxy.NO_PROXY); + } + return ret; + } + + private static Proxy proxyFromHostPort(Proxy.Type type, String hostPortString) { + try { + String[] hostPort = hostPortString.split(":"); + String host = hostPort[0]; + int port = Integer.parseInt(hostPort[1]); + return new Proxy(type, InetSocketAddress.createUnresolved(host, port)); + } catch (NumberFormatException|ArrayIndexOutOfBoundsException e) { + Log.d(TAG, "Unable to parse proxy " + hostPortString + " " + e); + return null; + } + } + + @Override + public void connectFailed(URI uri, SocketAddress address, IOException failure) { + + } + +} diff --git a/framework/src/android/net/Proxy.java b/framework/src/android/net/Proxy.java new file mode 100644 index 0000000000..03b07e080a --- /dev/null +++ b/framework/src/android/net/Proxy.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2007 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 android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + +import com.android.net.module.util.ProxyUtils; + +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.URI; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A convenience class for accessing the user and default proxy + * settings. + */ +public final class Proxy { + + private static final String TAG = "Proxy"; + + private static final ProxySelector sDefaultProxySelector; + + /** + * Used to notify an app that's caching the proxy that either the default + * connection has changed or any connection's proxy has changed. The new + * proxy should be queried using {@link ConnectivityManager#getDefaultProxy()}. + * + *

This is a protected intent that can only be sent by the system + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + /** + * Intent extra included with {@link #PROXY_CHANGE_ACTION} intents. + * It describes the new proxy being used (as a {@link ProxyInfo} object). + * @deprecated Because {@code PROXY_CHANGE_ACTION} is sent whenever the proxy + * for any network on the system changes, applications should always use + * {@link ConnectivityManager#getDefaultProxy()} or + * {@link ConnectivityManager#getLinkProperties(Network)}.{@link LinkProperties#getHttpProxy()} + * to get the proxy for the Network(s) they are using. + */ + @Deprecated + public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; + + /** @hide */ + public static final int PROXY_VALID = 0; + /** @hide */ + public static final int PROXY_HOSTNAME_EMPTY = 1; + /** @hide */ + public static final int PROXY_HOSTNAME_INVALID = 2; + /** @hide */ + public static final int PROXY_PORT_EMPTY = 3; + /** @hide */ + public static final int PROXY_PORT_INVALID = 4; + /** @hide */ + public static final int PROXY_EXCLLIST_INVALID = 5; + + private static ConnectivityManager sConnectivityManager = null; + + // Hostname / IP REGEX validation + // Matches blank input, ips, and domain names + private static final String NAME_IP_REGEX = + "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*"; + + private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$"; + + private static final Pattern HOSTNAME_PATTERN; + + private static final String EXCL_REGEX = + "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*"; + + private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$"; + + private static final Pattern EXCLLIST_PATTERN; + + static { + HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); + EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); + sDefaultProxySelector = ProxySelector.getDefault(); + } + + /** + * Return the proxy object to be used for the URL given as parameter. + * @param ctx A Context used to get the settings for the proxy host. + * @param url A URL to be accessed. Used to evaluate exclusion list. + * @return Proxy (java.net) object containing the host name. If the + * user did not set a hostname it returns the default host. + * A null value means that no host is to be used. + * {@hide} + */ + @UnsupportedAppUsage + public static final java.net.Proxy getProxy(Context ctx, String url) { + String host = ""; + if ((url != null) && !isLocalHost(host)) { + URI uri = URI.create(url); + ProxySelector proxySelector = ProxySelector.getDefault(); + + List proxyList = proxySelector.select(uri); + + if (proxyList.size() > 0) { + return proxyList.get(0); + } + } + return java.net.Proxy.NO_PROXY; + } + + + /** + * Return the proxy host set by the user. + * @param ctx A Context used to get the settings for the proxy host. + * @return String containing the host name. If the user did not set a host + * name it returns the default host. A null value means that no + * host is to be used. + * @deprecated Use standard java vm proxy values to find the host, port + * and exclusion list. This call ignores the exclusion list. + */ + @Deprecated + public static final String getHost(Context ctx) { + java.net.Proxy proxy = getProxy(ctx, null); + if (proxy == java.net.Proxy.NO_PROXY) return null; + try { + return ((InetSocketAddress)(proxy.address())).getHostName(); + } catch (Exception e) { + return null; + } + } + + /** + * Return the proxy port set by the user. + * @param ctx A Context used to get the settings for the proxy port. + * @return The port number to use or -1 if no proxy is to be used. + * @deprecated Use standard java vm proxy values to find the host, port + * and exclusion list. This call ignores the exclusion list. + */ + @Deprecated + public static final int getPort(Context ctx) { + java.net.Proxy proxy = getProxy(ctx, null); + if (proxy == java.net.Proxy.NO_PROXY) return -1; + try { + return ((InetSocketAddress)(proxy.address())).getPort(); + } catch (Exception e) { + return -1; + } + } + + /** + * Return the default proxy host specified by the carrier. + * @return String containing the host name or null if there is no proxy for + * this carrier. + * @deprecated Use standard java vm proxy values to find the host, port and + * exclusion list. This call ignores the exclusion list and no + * longer reports only mobile-data apn-based proxy values. + */ + @Deprecated + public static final String getDefaultHost() { + String host = System.getProperty("http.proxyHost"); + if (TextUtils.isEmpty(host)) return null; + return host; + } + + /** + * Return the default proxy port specified by the carrier. + * @return The port number to be used with the proxy host or -1 if there is + * no proxy for this carrier. + * @deprecated Use standard java vm proxy values to find the host, port and + * exclusion list. This call ignores the exclusion list and no + * longer reports only mobile-data apn-based proxy values. + */ + @Deprecated + public static final int getDefaultPort() { + if (getDefaultHost() == null) return -1; + try { + return Integer.parseInt(System.getProperty("http.proxyPort")); + } catch (NumberFormatException e) { + return -1; + } + } + + private static final boolean isLocalHost(String host) { + if (host == null) { + return false; + } + try { + if (host != null) { + if (host.equalsIgnoreCase("localhost")) { + return true; + } + if (InetAddresses.parseNumericAddress(host).isLoopbackAddress()) { + return true; + } + } + } catch (IllegalArgumentException iex) { + } + return false; + } + + /** + * Validate syntax of hostname, port and exclusion list entries + * {@hide} + */ + public static int validate(String hostname, String port, String exclList) { + Matcher match = HOSTNAME_PATTERN.matcher(hostname); + Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList); + + if (!match.matches()) return PROXY_HOSTNAME_INVALID; + + if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID; + + if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY; + + if (port.length() > 0) { + if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY; + int portVal = -1; + try { + portVal = Integer.parseInt(port); + } catch (NumberFormatException ex) { + return PROXY_PORT_INVALID; + } + if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID; + } + return PROXY_VALID; + } + + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final void setHttpProxySystemProperty(ProxyInfo p) { + String host = null; + String port = null; + String exclList = null; + Uri pacFileUrl = Uri.EMPTY; + if (p != null) { + host = p.getHost(); + port = Integer.toString(p.getPort()); + exclList = ProxyUtils.exclusionListAsString(p.getExclusionList()); + pacFileUrl = p.getPacFileUrl(); + } + setHttpProxySystemProperty(host, port, exclList, pacFileUrl); + } + + /** @hide */ + public static final void setHttpProxySystemProperty(String host, String port, String exclList, + Uri pacFileUrl) { + if (exclList != null) exclList = exclList.replace(",", "|"); + if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); + if (host != null) { + System.setProperty("http.proxyHost", host); + System.setProperty("https.proxyHost", host); + } else { + System.clearProperty("http.proxyHost"); + System.clearProperty("https.proxyHost"); + } + if (port != null) { + System.setProperty("http.proxyPort", port); + System.setProperty("https.proxyPort", port); + } else { + System.clearProperty("http.proxyPort"); + System.clearProperty("https.proxyPort"); + } + if (exclList != null) { + System.setProperty("http.nonProxyHosts", exclList); + System.setProperty("https.nonProxyHosts", exclList); + } else { + System.clearProperty("http.nonProxyHosts"); + System.clearProperty("https.nonProxyHosts"); + } + if (!Uri.EMPTY.equals(pacFileUrl)) { + ProxySelector.setDefault(new PacProxySelector()); + } else { + ProxySelector.setDefault(sDefaultProxySelector); + } + } +} diff --git a/framework/src/android/net/ProxyInfo.aidl b/framework/src/android/net/ProxyInfo.aidl new file mode 100644 index 0000000000..a5d0c120e7 --- /dev/null +++ b/framework/src/android/net/ProxyInfo.aidl @@ -0,0 +1,21 @@ +/* +** +** Copyright (C) 2010 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; + +@JavaOnlyStableParcelable parcelable ProxyInfo; + diff --git a/framework/src/android/net/ProxyInfo.java b/framework/src/android/net/ProxyInfo.java new file mode 100644 index 0000000000..9c9fed1028 --- /dev/null +++ b/framework/src/android/net/ProxyInfo.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2010 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.net.InetSocketAddress; +import java.net.URLConnection; +import java.util.List; +import java.util.Locale; + +/** + * Describes a proxy configuration. + * + * Proxy configurations are already integrated within the {@code java.net} and + * Apache HTTP stack. So {@link URLConnection} and Apache's {@code HttpClient} will use + * them automatically. + * + * Other HTTP stacks will need to obtain the proxy info from + * {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}. + */ +public class ProxyInfo implements Parcelable { + + private final String mHost; + private final int mPort; + private final String mExclusionList; + private final String[] mParsedExclusionList; + private final Uri mPacFileUrl; + + /** + *@hide + */ + public static final String LOCAL_EXCL_LIST = ""; + /** + *@hide + */ + public static final int LOCAL_PORT = -1; + /** + *@hide + */ + public static final String LOCAL_HOST = "localhost"; + + /** + * Constructs a {@link ProxyInfo} object that points at a Direct proxy + * on the specified host and port. + */ + public static ProxyInfo buildDirectProxy(String host, int port) { + return new ProxyInfo(host, port, null); + } + + /** + * Constructs a {@link ProxyInfo} object that points at a Direct proxy + * on the specified host and port. + * + * The proxy will not be used to access any host in exclusion list, exclList. + * + * @param exclList Hosts to exclude using the proxy on connections for. These + * hosts can use wildcards such as *.example.com. + */ + public static ProxyInfo buildDirectProxy(String host, int port, List exclList) { + String[] array = exclList.toArray(new String[exclList.size()]); + return new ProxyInfo(host, port, TextUtils.join(",", array), array); + } + + /** + * Construct a {@link ProxyInfo} that will download and run the PAC script + * at the specified URL. + */ + public static ProxyInfo buildPacProxy(Uri pacUri) { + return new ProxyInfo(pacUri); + } + + /** + * Construct a {@link ProxyInfo} object that will download and run the PAC script at the + * specified URL and port. + */ + @NonNull + public static ProxyInfo buildPacProxy(@NonNull Uri pacUrl, int port) { + return new ProxyInfo(pacUrl, port); + } + + /** + * Create a ProxyProperties that points at a HTTP Proxy. + * @hide + */ + @UnsupportedAppUsage + public ProxyInfo(String host, int port, String exclList) { + mHost = host; + mPort = port; + mExclusionList = exclList; + mParsedExclusionList = parseExclusionList(mExclusionList); + mPacFileUrl = Uri.EMPTY; + } + + /** + * Create a ProxyProperties that points at a PAC URL. + * @hide + */ + public ProxyInfo(@NonNull Uri pacFileUrl) { + mHost = LOCAL_HOST; + mPort = LOCAL_PORT; + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); + if (pacFileUrl == null) { + throw new NullPointerException(); + } + mPacFileUrl = pacFileUrl; + } + + /** + * Only used in PacProxyInstaller after Local Proxy is bound. + * @hide + */ + public ProxyInfo(@NonNull Uri pacFileUrl, int localProxyPort) { + mHost = LOCAL_HOST; + mPort = localProxyPort; + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); + if (pacFileUrl == null) { + throw new NullPointerException(); + } + mPacFileUrl = pacFileUrl; + } + + private static String[] parseExclusionList(String exclusionList) { + if (exclusionList == null) { + return new String[0]; + } else { + return exclusionList.toLowerCase(Locale.ROOT).split(","); + } + } + + private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) { + mHost = host; + mPort = port; + mExclusionList = exclList; + mParsedExclusionList = parsedExclList; + mPacFileUrl = Uri.EMPTY; + } + + /** + * A copy constructor to hold proxy properties. + */ + public ProxyInfo(@Nullable ProxyInfo source) { + if (source != null) { + mHost = source.getHost(); + mPort = source.getPort(); + mPacFileUrl = source.mPacFileUrl; + mExclusionList = source.getExclusionListAsString(); + mParsedExclusionList = source.mParsedExclusionList; + } else { + mHost = null; + mPort = 0; + mExclusionList = null; + mParsedExclusionList = null; + mPacFileUrl = Uri.EMPTY; + } + } + + /** + * @hide + */ + public InetSocketAddress getSocketAddress() { + InetSocketAddress inetSocketAddress = null; + try { + inetSocketAddress = new InetSocketAddress(mHost, mPort); + } catch (IllegalArgumentException e) { } + return inetSocketAddress; + } + + /** + * Returns the URL of the current PAC script or null if there is + * no PAC script. + */ + public Uri getPacFileUrl() { + return mPacFileUrl; + } + + /** + * When configured to use a Direct Proxy this returns the host + * of the proxy. + */ + public String getHost() { + return mHost; + } + + /** + * When configured to use a Direct Proxy this returns the port + * of the proxy + */ + public int getPort() { + return mPort; + } + + /** + * When configured to use a Direct Proxy this returns the list + * of hosts for which the proxy is ignored. + */ + public String[] getExclusionList() { + return mParsedExclusionList; + } + + /** + * comma separated + * @hide + */ + @Nullable + public String getExclusionListAsString() { + return mExclusionList; + } + + /** + * Return true if the pattern of proxy is valid, otherwise return false. + */ + public boolean isValid() { + if (!Uri.EMPTY.equals(mPacFileUrl)) return true; + return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, + mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); + } + + /** + * @hide + */ + public java.net.Proxy makeProxy() { + java.net.Proxy proxy = java.net.Proxy.NO_PROXY; + if (mHost != null) { + try { + InetSocketAddress inetSocketAddress = new InetSocketAddress(mHost, mPort); + proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, inetSocketAddress); + } catch (IllegalArgumentException e) { + } + } + return proxy; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (!Uri.EMPTY.equals(mPacFileUrl)) { + sb.append("PAC Script: "); + sb.append(mPacFileUrl); + } + if (mHost != null) { + sb.append("["); + sb.append(mHost); + sb.append("] "); + sb.append(Integer.toString(mPort)); + if (mExclusionList != null) { + sb.append(" xl=").append(mExclusionList); + } + } else { + sb.append("[ProxyProperties.mHost == null]"); + } + return sb.toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (!(o instanceof ProxyInfo)) return false; + ProxyInfo p = (ProxyInfo)o; + // If PAC URL is present in either then they must be equal. + // Other parameters will only be for fall back. + if (!Uri.EMPTY.equals(mPacFileUrl)) { + return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort; + } + if (!Uri.EMPTY.equals(p.mPacFileUrl)) { + return false; + } + if (mExclusionList != null && !mExclusionList.equals(p.getExclusionListAsString())) { + return false; + } + if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) { + return false; + } + if (mHost != null && p.mHost == null) return false; + if (mHost == null && p.mHost != null) return false; + if (mPort != p.mPort) return false; + return true; + } + + /** + * Implement the Parcelable interface + * @hide + */ + public int describeContents() { + return 0; + } + + @Override + /* + * generate hashcode based on significant fields + */ + public int hashCode() { + return ((null == mHost) ? 0 : mHost.hashCode()) + + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) + + mPort; + } + + /** + * Implement the Parcelable interface. + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + if (!Uri.EMPTY.equals(mPacFileUrl)) { + dest.writeByte((byte)1); + mPacFileUrl.writeToParcel(dest, 0); + dest.writeInt(mPort); + return; + } else { + dest.writeByte((byte)0); + } + if (mHost != null) { + dest.writeByte((byte)1); + dest.writeString(mHost); + dest.writeInt(mPort); + } else { + dest.writeByte((byte)0); + } + dest.writeString(mExclusionList); + dest.writeStringArray(mParsedExclusionList); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public ProxyInfo createFromParcel(Parcel in) { + String host = null; + int port = 0; + if (in.readByte() != 0) { + Uri url = Uri.CREATOR.createFromParcel(in); + int localPort = in.readInt(); + return new ProxyInfo(url, localPort); + } + if (in.readByte() != 0) { + host = in.readString(); + port = in.readInt(); + } + String exclList = in.readString(); + String[] parsedExclList = in.createStringArray(); + ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); + return proxyProperties; + } + + public ProxyInfo[] newArray(int size) { + return new ProxyInfo[size]; + } + }; +} diff --git a/framework/src/android/net/RouteInfo.aidl b/framework/src/android/net/RouteInfo.aidl new file mode 100644 index 0000000000..7af9fdaef3 --- /dev/null +++ b/framework/src/android/net/RouteInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2011 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; + +@JavaOnlyStableParcelable parcelable RouteInfo; diff --git a/framework/src/android/net/RouteInfo.java b/framework/src/android/net/RouteInfo.java new file mode 100644 index 0000000000..5b6684ace0 --- /dev/null +++ b/framework/src/android/net/RouteInfo.java @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2011 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 android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.net.module.util.NetUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Objects; + +/** + * Represents a network route. + *

+ * This is used both to describe static network configuration and live network + * configuration information. + * + * A route contains three pieces of information: + *

    + *
  • a destination {@link IpPrefix} specifying the network destinations covered by this route. + * If this is {@code null} it indicates a default route of the address family (IPv4 or IPv6) + * implied by the gateway IP address. + *
  • a gateway {@link InetAddress} indicating the next hop to use. If this is {@code null} it + * indicates a directly-connected route. + *
  • an interface (which may be unspecified). + *
+ * Either the destination or the gateway may be {@code null}, but not both. If the + * destination and gateway are both specified, they must be of the same address family + * (IPv4 or IPv6). + */ +public final class RouteInfo implements Parcelable { + /** @hide */ + @IntDef(value = { + RTN_UNICAST, + RTN_UNREACHABLE, + RTN_THROW, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RouteType {} + + /** + * The IP destination address for this route. + */ + @NonNull + private final IpPrefix mDestination; + + /** + * The gateway address for this route. + */ + @UnsupportedAppUsage + @Nullable + private final InetAddress mGateway; + + /** + * The interface for this route. + */ + @Nullable + private final String mInterface; + + + /** Unicast route. @hide */ + @SystemApi + public static final int RTN_UNICAST = 1; + + /** Unreachable route. @hide */ + @SystemApi + public static final int RTN_UNREACHABLE = 7; + + /** Throw route. @hide */ + @SystemApi + public static final int RTN_THROW = 9; + + /** + * The type of this route; one of the RTN_xxx constants above. + */ + private final int mType; + + /** + * The maximum transmission unit size for this route. + */ + private final int mMtu; + + // Derived data members. + // TODO: remove these. + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private final boolean mIsHost; + private final boolean mHasGateway; + + /** + * Constructs a RouteInfo object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of + * {@link Inet6Address}. + *

+ * destination and gateway may not both be null. + * + * @param destination the destination prefix + * @param gateway the IP address to route packets through + * @param iface the interface name to send packets on + * @param type the type of this route + * + * @hide + */ + @SystemApi + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface, @RouteType int type) { + this(destination, gateway, iface, type, 0); + } + + /** + * Constructs a RouteInfo object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of + * {@link Inet6Address}. + *

+ * destination and gateway may not both be null. + * + * @param destination the destination prefix + * @param gateway the IP address to route packets through + * @param iface the interface name to send packets on + * @param type the type of this route + * @param mtu the maximum transmission unit size for this route + * + * @hide + */ + @SystemApi + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface, @RouteType int type, int mtu) { + switch (type) { + case RTN_UNICAST: + case RTN_UNREACHABLE: + case RTN_THROW: + // TODO: It would be nice to ensure that route types that don't have nexthops or + // interfaces, such as unreachable or throw, can't be created if an interface or + // a gateway is specified. This is a bit too complicated to do at the moment + // because: + // + // - LinkProperties sets the interface on routes added to it, and modifies the + // interfaces of all the routes when its interface name changes. + // - Even when the gateway is null, we store a non-null gateway here. + // + // For now, we just rely on the code that sets routes to do things properly. + break; + default: + throw new IllegalArgumentException("Unknown route type " + type); + } + + if (destination == null) { + if (gateway != null) { + if (gateway instanceof Inet4Address) { + destination = new IpPrefix(Inet4Address.ANY, 0); + } else { + destination = new IpPrefix(Inet6Address.ANY, 0); + } + } else { + // no destination, no gateway. invalid. + throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," + + destination); + } + } + // TODO: set mGateway to null if there is no gateway. This is more correct, saves space, and + // matches the documented behaviour. Before we can do this we need to fix all callers (e.g., + // ConnectivityService) to stop doing things like r.getGateway().equals(), ... . + if (gateway == null) { + if (destination.getAddress() instanceof Inet4Address) { + gateway = Inet4Address.ANY; + } else { + gateway = Inet6Address.ANY; + } + } + mHasGateway = (!gateway.isAnyLocalAddress()); + + if ((destination.getAddress() instanceof Inet4Address + && !(gateway instanceof Inet4Address)) + || (destination.getAddress() instanceof Inet6Address + && !(gateway instanceof Inet6Address))) { + throw new IllegalArgumentException("address family mismatch in RouteInfo constructor"); + } + mDestination = destination; // IpPrefix objects are immutable. + mGateway = gateway; // InetAddress objects are immutable. + mInterface = iface; // Strings are immutable. + mType = type; + mIsHost = isHost(); + mMtu = mtu; + } + + /** + * Constructs a {@code RouteInfo} object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of {@link Inet6Address}. + *

+ * Destination and gateway may not both be null. + * + * @param destination the destination address and prefix in an {@link IpPrefix} + * @param gateway the {@link InetAddress} to route packets through + * @param iface the interface name to send packets on + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { + this(destination, gateway, iface, RTN_UNICAST); + } + + /** + * @hide + */ + @UnsupportedAppUsage + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway, + @Nullable String iface) { + this(destination == null ? null : + new IpPrefix(destination.getAddress(), destination.getPrefixLength()), + gateway, iface); + } + + /** + * Constructs a {@code RouteInfo} object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of {@link Inet6Address}. + *

+ * Destination and gateway may not both be null. + * + * @param destination the destination address and prefix in an {@link IpPrefix} + * @param gateway the {@link InetAddress} to route packets through + * + * @hide + */ + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway) { + this(destination, gateway, null); + } + + /** + * @hide + * + * TODO: Remove this. + */ + @UnsupportedAppUsage + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway) { + this(destination, gateway, null); + } + + /** + * Constructs a default {@code RouteInfo} object. + * + * @param gateway the {@link InetAddress} to route packets through + * + * @hide + */ + @UnsupportedAppUsage + public RouteInfo(@NonNull InetAddress gateway) { + this((IpPrefix) null, gateway, null); + } + + /** + * Constructs a {@code RouteInfo} object representing a direct connected subnet. + * + * @param destination the {@link IpPrefix} describing the address and prefix + * length of the subnet. + * + * @hide + */ + public RouteInfo(@NonNull IpPrefix destination) { + this(destination, null, null); + } + + /** + * @hide + */ + public RouteInfo(@NonNull LinkAddress destination) { + this(destination, null, null); + } + + /** + * @hide + */ + public RouteInfo(@NonNull IpPrefix destination, @RouteType int type) { + this(destination, null, null, type); + } + + /** + * @hide + */ + public static RouteInfo makeHostRoute(@NonNull InetAddress host, @Nullable String iface) { + return makeHostRoute(host, null, iface); + } + + /** + * @hide + */ + public static RouteInfo makeHostRoute(@Nullable InetAddress host, @Nullable InetAddress gateway, + @Nullable String iface) { + if (host == null) return null; + + if (host instanceof Inet4Address) { + return new RouteInfo(new IpPrefix(host, 32), gateway, iface); + } else { + return new RouteInfo(new IpPrefix(host, 128), gateway, iface); + } + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private boolean isHost() { + return (mDestination.getAddress() instanceof Inet4Address && + mDestination.getPrefixLength() == 32) || + (mDestination.getAddress() instanceof Inet6Address && + mDestination.getPrefixLength() == 128); + } + + /** + * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}. + * + * @return {@link IpPrefix} specifying the destination. This is never {@code null}. + */ + @NonNull + public IpPrefix getDestination() { + return mDestination; + } + + /** + * TODO: Convert callers to use IpPrefix and then remove. + * @hide + */ + @NonNull + public LinkAddress getDestinationLinkAddress() { + return new LinkAddress(mDestination.getAddress(), mDestination.getPrefixLength()); + } + + /** + * Retrieves the gateway or next hop {@link InetAddress} for this route. + * + * @return {@link InetAddress} specifying the gateway or next hop. This may be + * {@code null} for a directly-connected route." + */ + @Nullable + public InetAddress getGateway() { + return mGateway; + } + + /** + * Retrieves the interface used for this route if specified, else {@code null}. + * + * @return The name of the interface used for this route. + */ + @Nullable + public String getInterface() { + return mInterface; + } + + /** + * Retrieves the type of this route. + * + * @return The type of this route; one of the {@code RTN_xxx} constants defined in this class. + * + * @hide + */ + @SystemApi + @RouteType + public int getType() { + return mType; + } + + /** + * Retrieves the MTU size for this route. + * + * @return The MTU size, or 0 if it has not been set. + * @hide + */ + @SystemApi + public int getMtu() { + return mMtu; + } + + /** + * Indicates if this route is a default route (ie, has no destination specified). + * + * @return {@code true} if the destination has a prefix length of 0. + */ + public boolean isDefaultRoute() { + return mType == RTN_UNICAST && mDestination.getPrefixLength() == 0; + } + + /** + * Indicates if this route is an unreachable default route. + * + * @return {@code true} if it's an unreachable route with prefix length of 0. + * @hide + */ + private boolean isUnreachableDefaultRoute() { + return mType == RTN_UNREACHABLE && mDestination.getPrefixLength() == 0; + } + + /** + * Indicates if this route is an IPv4 default route. + * @hide + */ + public boolean isIPv4Default() { + return isDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; + } + + /** + * Indicates if this route is an IPv4 unreachable default route. + * @hide + */ + public boolean isIPv4UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; + } + + /** + * Indicates if this route is an IPv6 default route. + * @hide + */ + public boolean isIPv6Default() { + return isDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; + } + + /** + * Indicates if this route is an IPv6 unreachable default route. + * @hide + */ + public boolean isIPv6UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; + } + + /** + * Indicates if this route is a host route (ie, matches only a single host address). + * + * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6, + * respectively. + * @hide + */ + public boolean isHostRoute() { + return mIsHost; + } + + /** + * Indicates if this route has a next hop ({@code true}) or is directly-connected + * ({@code false}). + * + * @return {@code true} if a gateway is specified + */ + public boolean hasGateway() { + return mHasGateway; + } + + /** + * Determines whether the destination and prefix of this route includes the specified + * address. + * + * @param destination A {@link InetAddress} to test to see if it would match this route. + * @return {@code true} if the destination and prefix length cover the given address. + */ + public boolean matches(InetAddress destination) { + return mDestination.contains(destination); + } + + /** + * Find the route from a Collection of routes that best matches a given address. + * May return null if no routes are applicable. + * @param routes a Collection of RouteInfos to chose from + * @param dest the InetAddress your trying to get to + * @return the RouteInfo from the Collection that best fits the given address + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public static RouteInfo selectBestRoute(Collection routes, InetAddress dest) { + return NetUtils.selectBestRoute(routes, dest); + } + + /** + * Returns a human-readable description of this object. + */ + public String toString() { + String val = ""; + if (mDestination != null) val = mDestination.toString(); + if (mType == RTN_UNREACHABLE) { + val += " unreachable"; + } else if (mType == RTN_THROW) { + val += " throw"; + } else { + val += " ->"; + if (mGateway != null) val += " " + mGateway.getHostAddress(); + if (mInterface != null) val += " " + mInterface; + if (mType != RTN_UNICAST) { + val += " unknown type " + mType; + } + } + val += " mtu " + mMtu; + return val; + } + + /** + * Compares this RouteInfo object against the specified object and indicates if they are equal. + * @return {@code true} if the objects are equal, {@code false} otherwise. + */ + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + + if (!(obj instanceof RouteInfo)) return false; + + RouteInfo target = (RouteInfo) obj; + + return Objects.equals(mDestination, target.getDestination()) && + Objects.equals(mGateway, target.getGateway()) && + Objects.equals(mInterface, target.getInterface()) && + mType == target.getType() && mMtu == target.getMtu(); + } + + /** + * A helper class that contains the destination, the gateway and the interface in a + * {@code RouteInfo}, used by {@link ConnectivityService#updateRoutes} or + * {@link LinkProperties#addRoute} to calculate the list to be updated. + * {@code RouteInfo} objects with different interfaces are treated as different routes because + * *usually* on Android different interfaces use different routing tables, and moving a route + * to a new routing table never constitutes an update, but is always a remove and an add. + * + * @hide + */ + public static class RouteKey { + @NonNull private final IpPrefix mDestination; + @Nullable private final InetAddress mGateway; + @Nullable private final String mInterface; + + RouteKey(@NonNull IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { + mDestination = destination; + mGateway = gateway; + mInterface = iface; + } + + @Override + public boolean equals(@Nullable Object o) { + if (!(o instanceof RouteKey)) { + return false; + } + RouteKey p = (RouteKey) o; + // No need to do anything special for scoped addresses. Inet6Address#equals does not + // consider the scope ID, but the netd route IPCs (e.g., INetd#networkAddRouteParcel) + // and the kernel ignore scoped addresses both in the prefix and in the nexthop and only + // look at RTA_OIF. + return Objects.equals(p.mDestination, mDestination) + && Objects.equals(p.mGateway, mGateway) + && Objects.equals(p.mInterface, mInterface); + } + + @Override + public int hashCode() { + return Objects.hash(mDestination, mGateway, mInterface); + } + } + + /** + * Get {@code RouteKey} of this {@code RouteInfo}. + * @return a {@code RouteKey} object. + * + * @hide + */ + @NonNull + public RouteKey getRouteKey() { + return new RouteKey(mDestination, mGateway, mInterface); + } + + /** + * Returns a hashcode for this RouteInfo object. + */ + public int hashCode() { + return (mDestination.hashCode() * 41) + + (mGateway == null ? 0 :mGateway.hashCode() * 47) + + (mInterface == null ? 0 :mInterface.hashCode() * 67) + + (mType * 71) + (mMtu * 89); + } + + /** + * Implement the Parcelable interface + */ + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mDestination, flags); + byte[] gatewayBytes = (mGateway == null) ? null : mGateway.getAddress(); + dest.writeByteArray(gatewayBytes); + dest.writeString(mInterface); + dest.writeInt(mType); + dest.writeInt(mMtu); + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public RouteInfo createFromParcel(Parcel in) { + IpPrefix dest = in.readParcelable(null); + + InetAddress gateway = null; + byte[] addr = in.createByteArray(); + try { + gateway = InetAddress.getByAddress(addr); + } catch (UnknownHostException e) {} + + String iface = in.readString(); + int type = in.readInt(); + int mtu = in.readInt(); + + return new RouteInfo(dest, gateway, iface, type, mtu); + } + + public RouteInfo[] newArray(int size) { + return new RouteInfo[size]; + } + }; +} diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java new file mode 100644 index 0000000000..d007a9520c --- /dev/null +++ b/framework/src/android/net/SocketKeepalive.java @@ -0,0 +1,301 @@ +/* + * 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 android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive}, + * passing in a non-null callback. If the {@link SocketKeepalive} is successfully + * started, the callback's {@code onStarted} method will be called. If an error occurs, + * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this + * class. + * + * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call + * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or + * {@link SocketKeepalive.Callback#onError} if an error occurred. + * + * For cellular, the device MUST support at least 1 keepalive slot. + * + * For WiFi, the device SHOULD support keepalive offload. If it does not, it MUST reply with + * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload + * request. If it does, it MUST support at least 3 concurrent keepalive slots. + */ +public abstract class SocketKeepalive implements AutoCloseable { + static final String TAG = "SocketKeepalive"; + + /** + * No errors. + * @hide + */ + @SystemApi + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int DATA_RECEIVED = -2; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + /** The target socket is invalid. */ + public static final int ERROR_INVALID_SOCKET = -25; + /** The target socket is not idle. */ + public static final int ERROR_SOCKET_NOT_IDLE = -26; + /** + * The stop reason is uninitialized. This should only be internally used as initial state + * of stop reason, instead of propagating to application. + * @hide + */ + public static final int ERROR_STOP_REASON_UNINITIALIZED = -27; + + /** The device does not support this request. */ + public static final int ERROR_UNSUPPORTED = -30; + /** @hide TODO: delete when telephony code has been updated. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + /** The limitation of resource is reached. */ + public static final int ERROR_INSUFFICIENT_RESOURCES = -32; + + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_INVALID_NETWORK, + ERROR_INVALID_IP_ADDRESS, + ERROR_INVALID_PORT, + ERROR_INVALID_LENGTH, + ERROR_INVALID_INTERVAL, + ERROR_INVALID_SOCKET, + ERROR_SOCKET_NOT_IDLE + }) + public @interface ErrorCode {} + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + SUCCESS, + ERROR_INVALID_LENGTH, + ERROR_UNSUPPORTED, + ERROR_INSUFFICIENT_RESOURCES, + ERROR_HARDWARE_UNSUPPORTED + }) + public @interface KeepaliveEvent {} + + /** + * The minimum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MIN_INTERVAL_SEC = 10; + + /** + * The maximum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MAX_INTERVAL_SEC = 3600; + + /** + * An exception that embarks an error code. + * @hide + */ + public static class ErrorCodeException extends Exception { + public final int error; + public ErrorCodeException(final int error, final Throwable e) { + super(e); + this.error = error; + } + public ErrorCodeException(final int error) { + this.error = error; + } + } + + /** + * This socket is invalid. + * See the error code for details, and the optional cause. + * @hide + */ + public static class InvalidSocketException extends ErrorCodeException { + public InvalidSocketException(final int error, final Throwable e) { + super(error, e); + } + public InvalidSocketException(final int error) { + super(error); + } + } + + @NonNull final IConnectivityManager mService; + @NonNull final Network mNetwork; + @NonNull final ParcelFileDescriptor mPfd; + @NonNull final Executor mExecutor; + @NonNull final ISocketKeepaliveCallback mCallback; + // TODO: remove slot since mCallback could be used to identify which keepalive to stop. + @Nullable Integer mSlot; + + SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + @NonNull Executor executor, @NonNull Callback callback) { + mService = service; + mNetwork = network; + mPfd = pfd; + mExecutor = executor; + mCallback = new ISocketKeepaliveCallback.Stub() { + @Override + public void onStarted(int slot) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = slot; + callback.onStarted(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onStopped() { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onStopped(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onError(int error) { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onError(error); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onDataReceived() { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onDataReceived(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + } + + /** + * Request that keepalive be started with the given {@code intervalSec}. See + * {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an exception + * when invoking start or stop of the {@link SocketKeepalive}, a {@link RemoteException} will be + * thrown into the {@code executor}. This is typically not important to catch because the remote + * party is the system, so if it is not in shape to communicate through binder the system is + * probably going down anyway. If the caller cares regardless, it can use a custom + * {@link Executor} to catch the {@link RemoteException}. + * + * @param intervalSec The target interval in seconds between keepalive packet transmissions. + * The interval should be between 10 seconds and 3600 seconds, otherwise + * {@link #ERROR_INVALID_INTERVAL} will be returned. + */ + public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC) + int intervalSec) { + startImpl(intervalSec); + } + + abstract void startImpl(int intervalSec); + + /** + * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped} + * before using the object. See {@link SocketKeepalive}. + */ + public final void stop() { + stopImpl(); + } + + abstract void stopImpl(); + + /** + * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be + * usable again if {@code close()} is called. + */ + @Override + public final void close() { + stop(); + try { + mPfd.close(); + } catch (IOException e) { + // Nothing much can be done. + } + } + + /** + * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See + * {@link SocketKeepalive}. + */ + public static class Callback { + /** The requested keepalive was successfully started. */ + public void onStarted() {} + /** The keepalive was successfully stopped. */ + public void onStopped() {} + /** An error occurred. */ + public void onError(@ErrorCode int error) {} + /** The keepalive on a TCP socket was stopped because the socket received data. This is + * never called for UDP sockets. */ + public void onDataReceived() {} + } +} diff --git a/framework/src/android/net/StaticIpConfiguration.aidl b/framework/src/android/net/StaticIpConfiguration.aidl new file mode 100644 index 0000000000..8aac701fe7 --- /dev/null +++ b/framework/src/android/net/StaticIpConfiguration.aidl @@ -0,0 +1,20 @@ +/* +** +** 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; + +@JavaOnlyStableParcelable parcelable StaticIpConfiguration; \ No newline at end of file diff --git a/framework/src/android/net/StaticIpConfiguration.java b/framework/src/android/net/StaticIpConfiguration.java new file mode 100644 index 0000000000..ce545974f5 --- /dev/null +++ b/framework/src/android/net/StaticIpConfiguration.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2014 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; +import com.android.net.module.util.InetAddressUtils; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Class that describes static IP configuration. + * + *

This class is different from {@link LinkProperties} because it represents + * configuration intent. The general contract is that if we can represent + * a configuration here, then we should be able to configure it on a network. + * The intent is that it closely match the UI we have for configuring networks. + * + *

In contrast, {@link LinkProperties} represents current state. It is much more + * expressive. For example, it supports multiple IP addresses, multiple routes, + * stacked interfaces, and so on. Because LinkProperties is so expressive, + * using it to represent configuration intent as well as current state causes + * problems. For example, we could unknowingly save a configuration that we are + * not in fact capable of applying, or we could save a configuration that the + * UI cannot display, which has the potential for malicious code to hide + * hostile or unexpected configuration from the user. + * + * @hide + */ +@SystemApi +public final class StaticIpConfiguration implements Parcelable { + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public LinkAddress ipAddress; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public InetAddress gateway; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @NonNull + public final ArrayList dnsServers; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public String domains; + + public StaticIpConfiguration() { + dnsServers = new ArrayList<>(); + } + + public StaticIpConfiguration(@Nullable StaticIpConfiguration source) { + this(); + if (source != null) { + // All of these except dnsServers are immutable, so no need to make copies. + ipAddress = source.ipAddress; + gateway = source.gateway; + dnsServers.addAll(source.dnsServers); + domains = source.domains; + } + } + + public void clear() { + ipAddress = null; + gateway = null; + dnsServers.clear(); + domains = null; + } + + /** + * Get the static IP address included in the configuration. + */ + public @Nullable LinkAddress getIpAddress() { + return ipAddress; + } + + /** + * Get the gateway included in the configuration. + */ + public @Nullable InetAddress getGateway() { + return gateway; + } + + /** + * Get the DNS servers included in the configuration. + */ + public @NonNull List getDnsServers() { + return dnsServers; + } + + /** + * Get a {@link String} containing the comma separated domains to search when resolving host + * names on this link, in priority order. + */ + public @Nullable String getDomains() { + return domains; + } + + /** + * Helper class to build a new instance of {@link StaticIpConfiguration}. + */ + public static final class Builder { + private LinkAddress mIpAddress; + private InetAddress mGateway; + private Iterable mDnsServers; + private String mDomains; + + /** + * Set the IP address to be included in the configuration; null by default. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) { + mIpAddress = ipAddress; + return this; + } + + /** + * Set the address of the gateway to be included in the configuration; null by default. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setGateway(@Nullable InetAddress gateway) { + mGateway = gateway; + return this; + } + + /** + * Set the addresses of the DNS servers included in the configuration; empty by default. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setDnsServers(@NonNull Iterable dnsServers) { + Preconditions.checkNotNull(dnsServers); + mDnsServers = dnsServers; + return this; + } + + /** + * Sets the DNS domain search path to be used on the link; null by default. + * @param newDomains A {@link String} containing the comma separated domains to search when + * resolving host names on this link, in priority order. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setDomains(@Nullable String newDomains) { + mDomains = newDomains; + return this; + } + + /** + * Create a {@link StaticIpConfiguration} from the parameters in this {@link Builder}. + * @return The newly created StaticIpConfiguration. + */ + public @NonNull StaticIpConfiguration build() { + final StaticIpConfiguration config = new StaticIpConfiguration(); + config.ipAddress = mIpAddress; + config.gateway = mGateway; + if (mDnsServers != null) { + for (InetAddress server : mDnsServers) { + config.dnsServers.add(server); + } + } + config.domains = mDomains; + return config; + } + } + + /** + * Add a DNS server to this configuration. + */ + public void addDnsServer(@NonNull InetAddress server) { + dnsServers.add(server); + } + + /** + * Returns the network routes specified by this object. Will typically include a + * directly-connected route for the IP address's local subnet and a default route. + * @param iface Interface to include in the routes. + */ + public @NonNull List getRoutes(@Nullable String iface) { + List routes = new ArrayList(3); + if (ipAddress != null) { + RouteInfo connectedRoute = new RouteInfo(ipAddress, null, iface); + routes.add(connectedRoute); + // If the default gateway is not covered by the directly-connected route, also add a + // host route to the gateway as well. This configuration is arguably invalid, but it + // used to work in K and earlier, and other OSes appear to accept it. + if (gateway != null && !connectedRoute.matches(gateway)) { + routes.add(RouteInfo.makeHostRoute(gateway, iface)); + } + } + if (gateway != null) { + routes.add(new RouteInfo((IpPrefix) null, gateway, iface)); + } + return routes; + } + + /** + * Returns a LinkProperties object expressing the data in this object. Note that the information + * contained in the LinkProperties will not be a complete picture of the link's configuration, + * because any configuration information that is obtained dynamically by the network (e.g., + * IPv6 configuration) will not be included. + * @hide + */ + public @NonNull LinkProperties toLinkProperties(String iface) { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(iface); + if (ipAddress != null) { + lp.addLinkAddress(ipAddress); + } + for (RouteInfo route : getRoutes(iface)) { + lp.addRoute(route); + } + for (InetAddress dns : dnsServers) { + lp.addDnsServer(dns); + } + lp.setDomains(domains); + return lp; + } + + @NonNull + @Override + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("IP address "); + if (ipAddress != null ) str.append(ipAddress).append(" "); + + str.append("Gateway "); + if (gateway != null) str.append(gateway.getHostAddress()).append(" "); + + str.append(" DNS servers: ["); + for (InetAddress dnsServer : dnsServers) { + str.append(" ").append(dnsServer.getHostAddress()); + } + + str.append(" ] Domains "); + if (domains != null) str.append(domains); + return str.toString(); + } + + @Override + public int hashCode() { + int result = 13; + result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode()); + result = 47 * result + (gateway == null ? 0 : gateway.hashCode()); + result = 47 * result + (domains == null ? 0 : domains.hashCode()); + result = 47 * result + dnsServers.hashCode(); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + + if (!(obj instanceof StaticIpConfiguration)) return false; + + StaticIpConfiguration other = (StaticIpConfiguration) obj; + + return other != null && + Objects.equals(ipAddress, other.ipAddress) && + Objects.equals(gateway, other.gateway) && + dnsServers.equals(other.dnsServers) && + Objects.equals(domains, other.domains); + } + + /** Implement the Parcelable interface */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public StaticIpConfiguration createFromParcel(Parcel in) { + return readFromParcel(in); + } + + public StaticIpConfiguration[] newArray(int size) { + return new StaticIpConfiguration[size]; + } + }; + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(ipAddress, flags); + InetAddressUtils.parcelInetAddress(dest, gateway, flags); + dest.writeInt(dnsServers.size()); + for (InetAddress dnsServer : dnsServers) { + InetAddressUtils.parcelInetAddress(dest, dnsServer, flags); + } + dest.writeString(domains); + } + + /** @hide */ + public static StaticIpConfiguration readFromParcel(Parcel in) { + final StaticIpConfiguration s = new StaticIpConfiguration(); + s.ipAddress = in.readParcelable(null); + s.gateway = InetAddressUtils.unparcelInetAddress(in); + s.dnsServers.clear(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + s.dnsServers.add(InetAddressUtils.unparcelInetAddress(in)); + } + s.domains = in.readString(); + return s; + } +} diff --git a/framework/src/android/net/TcpKeepalivePacketData.java b/framework/src/android/net/TcpKeepalivePacketData.java new file mode 100644 index 0000000000..ddb3a6a72f --- /dev/null +++ b/framework/src/android/net/TcpKeepalivePacketData.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.net.InetAddress; +import java.util.Objects; + +/** + * Represents the actual tcp keep alive packets which will be used for hardware offload. + * @hide + */ +@SystemApi +public final class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable { + private static final String TAG = "TcpKeepalivePacketData"; + + /** TCP sequence number. */ + public final int tcpSeq; + + /** TCP ACK number. */ + public final int tcpAck; + + /** TCP RCV window. */ + public final int tcpWindow; + + /** TCP RCV window scale. */ + public final int tcpWindowScale; + + /** IP TOS. */ + public final int ipTos; + + /** IP TTL. */ + public final int ipTtl; + + public TcpKeepalivePacketData(@NonNull final InetAddress srcAddress, int srcPort, + @NonNull final InetAddress dstAddress, int dstPort, @NonNull final byte[] data, + int tcpSeq, int tcpAck, int tcpWindow, int tcpWindowScale, int ipTos, int ipTtl) + throws InvalidPacketException { + super(srcAddress, srcPort, dstAddress, dstPort, data); + this.tcpSeq = tcpSeq; + this.tcpAck = tcpAck; + this.tcpWindow = tcpWindow; + this.tcpWindowScale = tcpWindowScale; + this.ipTos = ipTos; + this.ipTtl = ipTtl; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof TcpKeepalivePacketData)) return false; + final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o; + final InetAddress srcAddress = getSrcAddress(); + final InetAddress dstAddress = getDstAddress(); + return srcAddress.equals(other.getSrcAddress()) + && dstAddress.equals(other.getDstAddress()) + && getSrcPort() == other.getSrcPort() + && getDstPort() == other.getDstPort() + && this.tcpAck == other.tcpAck + && this.tcpSeq == other.tcpSeq + && this.tcpWindow == other.tcpWindow + && this.tcpWindowScale == other.tcpWindowScale + && this.ipTos == other.ipTos + && this.ipTtl == other.ipTtl; + } + + @Override + public int hashCode() { + return Objects.hash(getSrcAddress(), getDstAddress(), getSrcPort(), getDstPort(), + tcpAck, tcpSeq, tcpWindow, tcpWindowScale, ipTos, ipTtl); + } + + /** + * Parcelable Implementation. + * Note that this object implements parcelable (and needs to keep doing this as it inherits + * from a class that does), but should usually be parceled as a stable parcelable using + * the toStableParcelable() and fromStableParcelable() methods. + */ + @Override + public int describeContents() { + return 0; + } + + /** Write to parcel. */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(getSrcAddress().getHostAddress()); + out.writeString(getDstAddress().getHostAddress()); + out.writeInt(getSrcPort()); + out.writeInt(getDstPort()); + out.writeByteArray(getPacket()); + out.writeInt(tcpSeq); + out.writeInt(tcpAck); + out.writeInt(tcpWindow); + out.writeInt(tcpWindowScale); + out.writeInt(ipTos); + out.writeInt(ipTtl); + } + + private static TcpKeepalivePacketData readFromParcel(Parcel in) throws InvalidPacketException { + InetAddress srcAddress = InetAddresses.parseNumericAddress(in.readString()); + InetAddress dstAddress = InetAddresses.parseNumericAddress(in.readString()); + int srcPort = in.readInt(); + int dstPort = in.readInt(); + byte[] packet = in.createByteArray(); + int tcpSeq = in.readInt(); + int tcpAck = in.readInt(); + int tcpWnd = in.readInt(); + int tcpWndScale = in.readInt(); + int ipTos = in.readInt(); + int ipTtl = in.readInt(); + return new TcpKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, packet, tcpSeq, + tcpAck, tcpWnd, tcpWndScale, ipTos, ipTtl); + } + + /** Parcelable Creator. */ + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public TcpKeepalivePacketData createFromParcel(Parcel in) { + try { + return readFromParcel(in); + } catch (InvalidPacketException e) { + throw new IllegalArgumentException( + "Invalid TCP keepalive data: " + e.getError()); + } + } + + public TcpKeepalivePacketData[] newArray(int size) { + return new TcpKeepalivePacketData[size]; + } + }; + + @Override + public String toString() { + return "saddr: " + getSrcAddress() + + " daddr: " + getDstAddress() + + " sport: " + getSrcPort() + + " dport: " + getDstPort() + + " seq: " + tcpSeq + + " ack: " + tcpAck + + " window: " + tcpWindow + + " windowScale: " + tcpWindowScale + + " tos: " + ipTos + + " ttl: " + ipTtl; + } +} diff --git a/framework/src/android/net/TcpRepairWindow.java b/framework/src/android/net/TcpRepairWindow.java new file mode 100644 index 0000000000..f062fa9034 --- /dev/null +++ b/framework/src/android/net/TcpRepairWindow.java @@ -0,0 +1,48 @@ +/* + * 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 android.annotation.SystemApi; + +/** + * Corresponds to C's {@code struct tcp_repair_window} from + * include/uapi/linux/tcp.h + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TcpRepairWindow { + public final int sndWl1; + public final int sndWnd; + public final int maxWindow; + public final int rcvWnd; + public final int rcvWup; + public final int rcvWndScale; + + /** + * Constructs an instance with the given field values. + */ + public TcpRepairWindow(final int sndWl1, final int sndWnd, final int maxWindow, + final int rcvWnd, final int rcvWup, final int rcvWndScale) { + this.sndWl1 = sndWl1; + this.sndWnd = sndWnd; + this.maxWindow = maxWindow; + this.rcvWnd = rcvWnd; + this.rcvWup = rcvWup; + this.rcvWndScale = rcvWndScale; + } +} diff --git a/framework/src/android/net/TcpSocketKeepalive.java b/framework/src/android/net/TcpSocketKeepalive.java new file mode 100644 index 0000000000..d89814d49b --- /dev/null +++ b/framework/src/android/net/TcpSocketKeepalive.java @@ -0,0 +1,77 @@ +/* + * 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 android.annotation.NonNull; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; + +import java.util.concurrent.Executor; + +/** @hide */ +final class TcpSocketKeepalive extends SocketKeepalive { + + TcpSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, pfd, executor, callback); + } + + /** + * Starts keepalives. {@code mSocket} must be a connected TCP socket. + * + * - The application must not write to or read from the socket after calling this method, until + * onDataReceived, onStopped, or onError are called. If it does, the keepalive will fail + * with {@link #ERROR_SOCKET_NOT_IDLE}, or {@code #ERROR_INVALID_SOCKET} if the socket + * experienced an error (as in poll(2) returned POLLERR or POLLHUP); if this happens, the data + * received from the socket may be invalid, and the socket can't be recovered. + * - If the socket has data in the send or receive buffer, then this call will fail with + * {@link #ERROR_SOCKET_NOT_IDLE} and can be retried after the data has been processed. + * An app could ensure this by using an application-layer protocol to receive acknowledgement + * that indicates all data has been delivered to server, e.g. HTTP 200 OK. + * Then the app could go into keepalive mode after reading all remaining data within the + * acknowledgement. + */ + @Override + void startImpl(int intervalSec) { + mExecutor.execute(() -> { + try { + mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } + + @Override + void stopImpl() { + mExecutor.execute(() -> { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } +} diff --git a/framework/src/android/net/TestNetworkInterface.aidl b/framework/src/android/net/TestNetworkInterface.aidl new file mode 100644 index 0000000000..e1f4f9f794 --- /dev/null +++ b/framework/src/android/net/TestNetworkInterface.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +/** @hide */ +parcelable TestNetworkInterface; diff --git a/framework/src/android/net/TestNetworkInterface.java b/framework/src/android/net/TestNetworkInterface.java new file mode 100644 index 0000000000..4449ff8018 --- /dev/null +++ b/framework/src/android/net/TestNetworkInterface.java @@ -0,0 +1,78 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +/** + * This class is used to return the interface name and fd of the test interface + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TestNetworkInterface implements Parcelable { + @NonNull + private final ParcelFileDescriptor mFileDescriptor; + @NonNull + private final String mInterfaceName; + + @Override + public int describeContents() { + return (mFileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE); + out.writeString(mInterfaceName); + } + + public TestNetworkInterface(@NonNull ParcelFileDescriptor pfd, @NonNull String intf) { + mFileDescriptor = pfd; + mInterfaceName = intf; + } + + private TestNetworkInterface(@NonNull Parcel in) { + mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + mInterfaceName = in.readString(); + } + + @NonNull + public ParcelFileDescriptor getFileDescriptor() { + return mFileDescriptor; + } + + @NonNull + public String getInterfaceName() { + return mInterfaceName; + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public TestNetworkInterface createFromParcel(Parcel in) { + return new TestNetworkInterface(in); + } + + public TestNetworkInterface[] newArray(int size) { + return new TestNetworkInterface[size]; + } + }; +} diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java new file mode 100644 index 0000000000..4e894143bf --- /dev/null +++ b/framework/src/android/net/TestNetworkManager.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2018 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.util.Preconditions; + +import java.util.Arrays; +import java.util.Collection; + +/** + * Class that allows creation and management of per-app, test-only networks + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public class TestNetworkManager { + /** + * Prefix for tun interfaces created by this class. + * @hide + */ + public static final String TEST_TUN_PREFIX = "testtun"; + + /** + * Prefix for tap interfaces created by this class. + * @hide + */ + public static final String TEST_TAP_PREFIX = "testtap"; + + @NonNull private static final String TAG = TestNetworkManager.class.getSimpleName(); + + @NonNull private final ITestNetworkManager mService; + + /** @hide */ + public TestNetworkManager(@NonNull ITestNetworkManager service) { + mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager"); + } + + /** + * Teardown the capability-limited, testing-only network for a given interface + * + * @param network The test network that should be torn down + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void teardownTestNetwork(@NonNull Network network) { + try { + mService.teardownTestNetwork(network.netId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void setupTestNetwork( + @NonNull String iface, + @Nullable LinkProperties lp, + boolean isMetered, + @NonNull int[] administratorUids, + @NonNull IBinder binder) { + try { + mService.setupTestNetwork(iface, lp, isMetered, administratorUids, binder); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets up a capability-limited, testing-only network for a given interface + * + * @param lp The LinkProperties for the TestNetworkService to use for this test network. Note + * that the interface name and link addresses will be overwritten, and the passed-in values + * discarded. + * @param isMetered Whether or not the network should be considered metered. + * @param binder A binder object guarding the lifecycle of this test network. + * @hide + */ + public void setupTestNetwork( + @NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) { + Preconditions.checkNotNull(lp, "Invalid LinkProperties"); + setupTestNetwork(lp.getInterfaceName(), lp, isMetered, new int[0], binder); + } + + /** + * Sets up a capability-limited, testing-only network for a given interface + * + * @param iface the name of the interface to be used for the Network LinkProperties. + * @param binder A binder object guarding the lifecycle of this test network. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) { + setupTestNetwork(iface, null, true, new int[0], binder); + } + + /** + * Sets up a capability-limited, testing-only network for a given interface with the given + * administrator UIDs. + * + * @param iface the name of the interface to be used for the Network LinkProperties. + * @param administratorUids The administrator UIDs to be used for the test-only network + * @param binder A binder object guarding the lifecycle of this test network. + * @hide + */ + public void setupTestNetwork( + @NonNull String iface, @NonNull int[] administratorUids, @NonNull IBinder binder) { + setupTestNetwork(iface, null, true, administratorUids, binder); + } + + /** + * Create a tun interface for testing purposes + * + * @param linkAddrs an array of LinkAddresses to assign to the TUN interface + * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the + * TUN interface. + * @deprecated Use {@link #createTunInterface(Collection)} instead. + * @hide + */ + @Deprecated + @NonNull + public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { + return createTunInterface(Arrays.asList(linkAddrs)); + } + + /** + * Create a tun interface for testing purposes + * + * @param linkAddrs an array of LinkAddresses to assign to the TUN interface + * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the + * TUN interface. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull + public TestNetworkInterface createTunInterface(@NonNull Collection linkAddrs) { + try { + final LinkAddress[] arr = new LinkAddress[linkAddrs.size()]; + return mService.createTunInterface(linkAddrs.toArray(arr)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Create a tap interface for testing purposes + * + * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the + * TAP interface. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull + public TestNetworkInterface createTapInterface() { + try { + return mService.createTapInterface(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + +} diff --git a/framework/src/android/net/TransportInfo.java b/framework/src/android/net/TransportInfo.java new file mode 100644 index 0000000000..aa4bbb0511 --- /dev/null +++ b/framework/src/android/net/TransportInfo.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 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 android.annotation.NonNull; +import android.annotation.SystemApi; + +/** + * A container for transport-specific capabilities which is returned by + * {@link NetworkCapabilities#getTransportInfo()}. Specific networks + * may provide concrete implementations of this interface. + * @see android.net.wifi.aware.WifiAwareNetworkInfo + * @see android.net.wifi.WifiInfo + */ +public interface TransportInfo { + + /** + * Create a copy of a {@link TransportInfo} that will preserve location sensitive fields that + * were set based on the permissions of the process that originally received it. + * + *

By default {@link TransportInfo} does not preserve such fields during parceling, as + * they should not be shared outside of the process that receives them without appropriate + * checks. + * + * @param parcelLocationSensitiveFields Whether the location sensitive fields should be kept + * when parceling + * @return Copy of this instance. + * @hide + */ + @SystemApi + @NonNull + default TransportInfo makeCopy(boolean parcelLocationSensitiveFields) { + return this; + } + + /** + * Returns whether this TransportInfo type has location sensitive fields or not (helps + * to determine whether to perform a location permission check or not before sending to + * apps). + * + * @return {@code true} if this instance contains location sensitive info, {@code false} + * otherwise. + * @hide + */ + @SystemApi + default boolean hasLocationSensitiveFields() { + return false; + } +} diff --git a/framework/src/android/net/UidRange.aidl b/framework/src/android/net/UidRange.aidl new file mode 100644 index 0000000000..f70fc8e2fe --- /dev/null +++ b/framework/src/android/net/UidRange.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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; + +/** + * An inclusive range of UIDs. + * + * {@hide} + */ +parcelable UidRange; \ No newline at end of file diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java new file mode 100644 index 0000000000..c87b8279c4 --- /dev/null +++ b/framework/src/android/net/VpnManager.java @@ -0,0 +1,164 @@ +/* + * 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 com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.RemoteException; + +import com.android.internal.net.VpnProfile; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.GeneralSecurityException; + +/** + * This class provides an interface for apps to manage platform VPN profiles + * + *

Apps can use this API to provide profiles with which the platform can set up a VPN without + * further app intermediation. When a VPN profile is present and the app is selected as an always-on + * VPN, the platform will directly trigger the negotiation of the VPN without starting or waking the + * app (unlike VpnService). + * + *

VPN apps using supported protocols should preferentially use this API over the {@link + * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user + * the guarantee that VPN network traffic is not subjected to on-device packet interception. + * + * @see Ikev2VpnProfile + */ +public class VpnManager { + /** Type representing a lack of VPN @hide */ + public static final int TYPE_VPN_NONE = -1; + /** VPN service type code @hide */ + public static final int TYPE_VPN_SERVICE = 1; + /** Platform VPN type code @hide */ + public static final int TYPE_VPN_PLATFORM = 2; + + /** @hide */ + @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM}) + @Retention(RetentionPolicy.SOURCE) + public @interface VpnType {} + + @NonNull private final Context mContext; + @NonNull private final IConnectivityManager mService; + + private static Intent getIntentForConfirmation() { + final Intent intent = new Intent(); + final ComponentName componentName = ComponentName.unflattenFromString( + Resources.getSystem().getString( + com.android.internal.R.string.config_platformVpnConfirmDialogComponent)); + intent.setComponent(componentName); + return intent; + } + + /** + * Create an instance of the VpnManager with the given context. + * + *

Internal only. Applications are expected to obtain an instance of the VpnManager via the + * {@link Context.getSystemService()} method call. + * + * @hide + */ + public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + mContext = checkNotNull(ctx, "missing Context"); + mService = checkNotNull(service, "missing IConnectivityManager"); + } + + /** + * Install a VpnProfile configuration keyed on the calling app's package name. + * + *

This method returns {@code null} if user consent has already been granted, or an {@link + * Intent} to a system activity. If an intent is returned, the application should launch the + * activity using {@link Activity#startActivityForResult} to request user consent. The activity + * may pop up a dialog to require user action, and the result will come back via its {@link + * Activity#onActivityResult}. If the result is {@link Activity#RESULT_OK}, the user has + * consented, and the VPN profile can be started. + * + * @param profile the VpnProfile provided by this package. Will override any previous VpnProfile + * stored for this package. + * @return an Intent requesting user consent to start the VPN, or null if consent is not + * required based on privileges or previous user consent. + */ + @Nullable + public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) { + final VpnProfile internalProfile; + + try { + internalProfile = profile.toVpnProfile(); + } catch (GeneralSecurityException | IOException e) { + // Conversion to VpnProfile failed; this is an invalid profile. Both of these exceptions + // indicate a failure to convert a PrivateKey or X509Certificate to a Base64 encoded + // string as required by the VpnProfile. + throw new IllegalArgumentException("Failed to serialize PlatformVpnProfile", e); + } + + try { + // Profile can never be null; it either gets set, or an exception is thrown. + if (mService.provisionVpnProfile(internalProfile, mContext.getOpPackageName())) { + return null; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return getIntentForConfirmation(); + } + + /** + * Delete the VPN profile configuration that was provisioned by the calling app + * + * @throws SecurityException if this would violate user settings + */ + public void deleteProvisionedVpnProfile() { + try { + mService.deleteVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request the startup of a previously provisioned VPN. + * + * @throws SecurityException exception if user or device settings prevent this VPN from being + * setup, or if user consent has not been granted + */ + public void startProvisionedVpnProfile() { + try { + mService.startVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Tear down the VPN provided by the calling app (if any) */ + public void stopProvisionedVpnProfile() { + try { + mService.stopVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/framework/src/android/net/VpnService.java b/framework/src/android/net/VpnService.java new file mode 100644 index 0000000000..8e90a119fe --- /dev/null +++ b/framework/src/android/net/VpnService.java @@ -0,0 +1,903 @@ +/* + * Copyright (C) 2011 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 android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.app.Activity; +import android.app.PendingIntent; +import android.app.Service; +import android.app.admin.DevicePolicyManager; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; + +import com.android.internal.net.VpnConfig; + +import java.net.DatagramSocket; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * VpnService is a base class for applications to extend and build their + * own VPN solutions. In general, it creates a virtual network interface, + * configures addresses and routing rules, and returns a file descriptor + * to the application. Each read from the descriptor retrieves an outgoing + * packet which was routed to the interface. Each write to the descriptor + * injects an incoming packet just like it was received from the interface. + * The interface is running on Internet Protocol (IP), so packets are + * always started with IP headers. The application then completes a VPN + * connection by processing and exchanging packets with the remote server + * over a tunnel. + * + *

Letting applications intercept packets raises huge security concerns. + * A VPN application can easily break the network. Besides, two of them may + * conflict with each other. The system takes several actions to address + * these issues. Here are some key points: + *

    + *
  • User action is required the first time an application creates a VPN + * connection.
  • + *
  • There can be only one VPN connection running at the same time. The + * existing interface is deactivated when a new one is created.
  • + *
  • A system-managed notification is shown during the lifetime of a + * VPN connection.
  • + *
  • A system-managed dialog gives the information of the current VPN + * connection. It also provides a button to disconnect.
  • + *
  • The network is restored automatically when the file descriptor is + * closed. It also covers the cases when a VPN application is crashed + * or killed by the system.
  • + *
+ * + *

There are two primary methods in this class: {@link #prepare} and + * {@link Builder#establish}. The former deals with user action and stops + * the VPN connection created by another application. The latter creates + * a VPN interface using the parameters supplied to the {@link Builder}. + * An application must call {@link #prepare} to grant the right to use + * other methods in this class, and the right can be revoked at any time. + * Here are the general steps to create a VPN connection: + *

    + *
  1. When the user presses the button to connect, call {@link #prepare} + * and launch the returned intent, if non-null.
  2. + *
  3. When the application becomes prepared, start the service.
  4. + *
  5. Create a tunnel to the remote server and negotiate the network + * parameters for the VPN connection.
  6. + *
  7. Supply those parameters to a {@link Builder} and create a VPN + * interface by calling {@link Builder#establish}.
  8. + *
  9. Process and exchange packets between the tunnel and the returned + * file descriptor.
  10. + *
  11. When {@link #onRevoke} is invoked, close the file descriptor and + * shut down the tunnel gracefully.
  12. + *
+ * + *

Services extending this class need to be declared with an appropriate + * permission and intent filter. Their access must be secured by + * {@link android.Manifest.permission#BIND_VPN_SERVICE} permission, and + * their intent filter must match {@link #SERVICE_INTERFACE} action. Here + * is an example of declaring a VPN service in {@code AndroidManifest.xml}: + *

+ * <service android:name=".ExampleVpnService"
+ *         android:permission="android.permission.BIND_VPN_SERVICE">
+ *     <intent-filter>
+ *         <action android:name="android.net.VpnService"/>
+ *     </intent-filter>
+ * </service>
+ * + *

The Android system starts a VPN in the background by calling + * {@link android.content.Context#startService startService()}. In Android 8.0 + * (API level 26) and higher, the system places VPN apps on the temporary + * allowlist for a short period so the app can start in the background. The VPN + * app must promote itself to the foreground after it's launched or the system + * will shut down the app. + * + *

Developer's guide

+ * + *

To learn more about developing VPN apps, read the + * VPN developer's guide. + * + * @see Builder + */ +public class VpnService extends Service { + + /** + * The action must be matched by the intent filter of this service. It also + * needs to require {@link android.Manifest.permission#BIND_VPN_SERVICE} + * permission so that other applications cannot abuse it. + */ + public static final String SERVICE_INTERFACE = VpnConfig.SERVICE_INTERFACE; + + /** + * Key for boolean meta-data field indicating whether this VpnService supports always-on mode. + * + *

For a VPN app targeting {@link android.os.Build.VERSION_CODES#N API 24} or above, Android + * provides users with the ability to set it as always-on, so that VPN connection is + * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device + * owner and profile owner apps through + * {@link DevicePolicyManager#setAlwaysOnVpnPackage}. + * + *

VPN apps not supporting this feature should opt out by adding this meta-data field to the + * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one + * {@code VpnService} component defined in {@code AndroidManifest.xml}, opting out any one of + * them will opt out the entire app. For example, + *

 {@code
+     * 
+     *     
+     *         
+     *     
+     *     
+     * 
+     * } 
+ * + *

This meta-data field defaults to {@code true} if absent. It will only have effect on + * {@link android.os.Build.VERSION_CODES#O_MR1} or higher. + */ + public static final String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = + "android.net.VpnService.SUPPORTS_ALWAYS_ON"; + + /** + * Use IConnectivityManager since those methods are hidden and not + * available in ConnectivityManager. + */ + private static IConnectivityManager getService() { + return IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + } + + /** + * Prepare to establish a VPN connection. This method returns {@code null} + * if the VPN application is already prepared or if the user has previously + * consented to the VPN application. Otherwise, it returns an + * {@link Intent} to a system activity. The application should launch the + * activity using {@link Activity#startActivityForResult} to get itself + * prepared. The activity may pop up a dialog to require user action, and + * the result will come back via its {@link Activity#onActivityResult}. + * If the result is {@link Activity#RESULT_OK}, the application becomes + * prepared and is granted to use other methods in this class. + * + *

Only one application can be granted at the same time. The right + * is revoked when another application is granted. The application + * losing the right will be notified via its {@link #onRevoke}. Unless + * it becomes prepared again, subsequent calls to other methods in this + * class will fail. + * + *

The user may disable the VPN at any time while it is activated, in + * which case this method will return an intent the next time it is + * executed to obtain the user's consent again. + * + * @see #onRevoke + */ + public static Intent prepare(Context context) { + try { + if (getService().prepareVpn(context.getPackageName(), null, context.getUserId())) { + return null; + } + } catch (RemoteException e) { + // ignore + } + return VpnConfig.getIntentForConfirmation(); + } + + /** + * Version of {@link #prepare(Context)} which does not require user consent. + * + *

Requires {@link android.Manifest.permission#CONTROL_VPN} and should generally not be + * used. Only acceptable in situations where user consent has been obtained through other means. + * + *

Once this is run, future preparations may be done with the standard prepare method as this + * will authorize the package to prepare the VPN without consent in the future. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CONTROL_VPN) + public static void prepareAndAuthorize(Context context) { + IConnectivityManager cm = getService(); + String packageName = context.getPackageName(); + try { + // Only prepare if we're not already prepared. + int userId = context.getUserId(); + if (!cm.prepareVpn(packageName, null, userId)) { + cm.prepareVpn(null, packageName, userId); + } + cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); + } catch (RemoteException e) { + // ignore + } + } + + /** + * Protect a socket from VPN connections. After protecting, data sent + * through this socket will go directly to the underlying network, + * so its traffic will not be forwarded through the VPN. + * This method is useful if some connections need to be kept + * outside of VPN. For example, a VPN tunnel should protect itself if its + * destination is covered by VPN routes. Otherwise its outgoing packets + * will be sent back to the VPN interface and cause an infinite loop. This + * method will fail if the application is not prepared or is revoked. + * + *

The socket is NOT closed by this method. + * + * @return {@code true} on success. + */ + public boolean protect(int socket) { + return NetworkUtils.protectFromVpn(socket); + } + + /** + * Convenience method to protect a {@link Socket} from VPN connections. + * + * @return {@code true} on success. + * @see #protect(int) + */ + public boolean protect(Socket socket) { + return protect(socket.getFileDescriptor$().getInt$()); + } + + /** + * Convenience method to protect a {@link DatagramSocket} from VPN + * connections. + * + * @return {@code true} on success. + * @see #protect(int) + */ + public boolean protect(DatagramSocket socket) { + return protect(socket.getFileDescriptor$().getInt$()); + } + + /** + * Adds a network address to the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is already in use or cannot be assigned to the interface for any other reason. + * + * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to + * be routed over the VPN. @see Builder#allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + * @see Builder#addAddress + * + * @hide + */ + public boolean addAddress(InetAddress address, int prefixLength) { + check(address, prefixLength); + try { + return getService().addVpnAddress(address.getHostAddress(), prefixLength); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Removes a network address from the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is not assigned to the VPN interface, or if it is the only address assigned (thus + * cannot be removed), or if the address cannot be removed for any other reason. + * + * After removing an address, if there are no addresses, routes or DNS servers of a particular + * address family (i.e., IPv4 or IPv6) configured on the VPN, that DOES NOT block that + * family from being routed. In other words, once an address family has been allowed, it stays + * allowed for the rest of the VPN's session. @see Builder#allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + * + * @hide + */ + public boolean removeAddress(InetAddress address, int prefixLength) { + check(address, prefixLength); + try { + return getService().removeVpnAddress(address.getHostAddress(), prefixLength); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + *

Used by the system to know the actual networks that carry traffic for apps affected by + * this VPN in order to present this information to the user (e.g., via status bar icons). + * + *

This method only needs to be called if the VPN has explicitly bound its underlying + * communications channels — such as the socket(s) passed to {@link #protect(int)} — + * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or + * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time + * the set of {@code Network}s it is using changes. + * + *

{@code networks} is one of the following: + *

    + *
  • a non-empty array: an array of one or more {@link Network}s, in + * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) + * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear + * first in the array.
  • + *
  • an empty array: a zero-element array, meaning that the VPN has no + * underlying network connection, and thus, app traffic will not be sent or received.
  • + *
  • null: (default) signifies that the VPN uses whatever is the system's + * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket} + * APIs mentioned above to send traffic over specific channels.
  • + *
+ * + *

This call will succeed only if the VPN is currently established. For setting this value + * when the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}. + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return {@code true} on success. + */ + public boolean setUnderlyingNetworks(Network[] networks) { + try { + return getService().setUnderlyingNetworksForVpn(networks); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Returns whether the service is running in always-on VPN mode. In this mode the system ensures + * that the service is always running by restarting it when necessary, e.g. after reboot. + * + * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set) + */ + public final boolean isAlwaysOn() { + try { + return getService().isCallerCurrentAlwaysOnVpnApp(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the service is running in always-on VPN lockdown mode. In this mode the + * system ensures that the service is always running and that the apps aren't allowed to bypass + * the VPN. + * + * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set) + */ + public final boolean isLockdownEnabled() { + try { + return getService().isCallerCurrentAlwaysOnVpnLockdownApp(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the communication interface to the service. This method returns + * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} + * action. Applications overriding this method must identify the intent + * and return the corresponding interface accordingly. + * + * @see Service#onBind + */ + @Override + public IBinder onBind(Intent intent) { + if (intent != null && SERVICE_INTERFACE.equals(intent.getAction())) { + return new Callback(); + } + return null; + } + + /** + * Invoked when the application is revoked. At this moment, the VPN + * interface is already deactivated by the system. The application should + * close the file descriptor and shut down gracefully. The default + * implementation of this method is calling {@link Service#stopSelf()}. + * + *

Calls to this method may not happen on the main thread + * of the process. + * + * @see #prepare + */ + public void onRevoke() { + stopSelf(); + } + + /** + * Use raw Binder instead of AIDL since now there is only one usage. + */ + private class Callback extends Binder { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { + if (code == IBinder.LAST_CALL_TRANSACTION) { + onRevoke(); + return true; + } + return false; + } + } + + /** + * Private method to validate address and prefixLength. + */ + private static void check(InetAddress address, int prefixLength) { + if (address.isLoopbackAddress()) { + throw new IllegalArgumentException("Bad address"); + } + if (address instanceof Inet4Address) { + if (prefixLength < 0 || prefixLength > 32) { + throw new IllegalArgumentException("Bad prefixLength"); + } + } else if (address instanceof Inet6Address) { + if (prefixLength < 0 || prefixLength > 128) { + throw new IllegalArgumentException("Bad prefixLength"); + } + } else { + throw new IllegalArgumentException("Unsupported family"); + } + } + + /** + * Helper class to create a VPN interface. This class should be always + * used within the scope of the outer {@link VpnService}. + * + * @see VpnService + */ + public class Builder { + + private final VpnConfig mConfig = new VpnConfig(); + @UnsupportedAppUsage + private final List mAddresses = new ArrayList(); + @UnsupportedAppUsage + private final List mRoutes = new ArrayList(); + + public Builder() { + mConfig.user = VpnService.this.getClass().getName(); + } + + /** + * Set the name of this session. It will be displayed in + * system-managed dialogs and notifications. This is recommended + * not required. + */ + @NonNull + public Builder setSession(@NonNull String session) { + mConfig.session = session; + return this; + } + + /** + * Set the {@link PendingIntent} to an activity for users to + * configure the VPN connection. If it is not set, the button + * to configure will not be shown in system-managed dialogs. + */ + @NonNull + public Builder setConfigureIntent(@NonNull PendingIntent intent) { + mConfig.configureIntent = intent; + return this; + } + + /** + * Set the maximum transmission unit (MTU) of the VPN interface. If + * it is not set, the default value in the operating system will be + * used. + * + * @throws IllegalArgumentException if the value is not positive. + */ + @NonNull + public Builder setMtu(int mtu) { + if (mtu <= 0) { + throw new IllegalArgumentException("Bad mtu"); + } + mConfig.mtu = mtu; + return this; + } + + /** + * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation + * and it is possible that some apps will ignore it. + */ + @NonNull + public Builder setHttpProxy(@NonNull ProxyInfo proxyInfo) { + mConfig.proxyInfo = proxyInfo; + return this; + } + + /** + * Add a network address to the VPN interface. Both IPv4 and IPv6 + * addresses are supported. At least one address must be set before + * calling {@link #establish}. + * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + */ + @NonNull + public Builder addAddress(@NonNull InetAddress address, int prefixLength) { + check(address, prefixLength); + + if (address.isAnyLocalAddress()) { + throw new IllegalArgumentException("Bad address"); + } + mAddresses.add(new LinkAddress(address, prefixLength)); + mConfig.updateAllowedFamilies(address); + return this; + } + + /** + * Convenience method to add a network address to the VPN interface + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * @see #addAddress(InetAddress, int) + */ + @NonNull + public Builder addAddress(@NonNull String address, int prefixLength) { + return addAddress(InetAddress.parseNumericAddress(address), prefixLength); + } + + /** + * Add a network route to the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + public Builder addRoute(@NonNull InetAddress address, int prefixLength) { + check(address, prefixLength); + + int offset = prefixLength / 8; + byte[] bytes = address.getAddress(); + if (offset < bytes.length) { + for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) { + if (bytes[offset] != 0) { + throw new IllegalArgumentException("Bad address"); + } + } + } + mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null)); + mConfig.updateAllowedFamilies(address); + return this; + } + + /** + * Convenience method to add a network route to the VPN interface + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the route is invalid. + * @see #addRoute(InetAddress, int) + */ + @NonNull + public Builder addRoute(@NonNull String address, int prefixLength) { + return addRoute(InetAddress.parseNumericAddress(address), prefixLength); + } + + /** + * Add a DNS server to the VPN connection. Both IPv4 and IPv6 + * addresses are supported. If none is set, the DNS servers of + * the default network will be used. + * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + */ + @NonNull + public Builder addDnsServer(@NonNull InetAddress address) { + if (address.isLoopbackAddress() || address.isAnyLocalAddress()) { + throw new IllegalArgumentException("Bad address"); + } + if (mConfig.dnsServers == null) { + mConfig.dnsServers = new ArrayList(); + } + mConfig.dnsServers.add(address.getHostAddress()); + return this; + } + + /** + * Convenience method to add a DNS server to the VPN connection + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * @see #addDnsServer(InetAddress) + */ + @NonNull + public Builder addDnsServer(@NonNull String address) { + return addDnsServer(InetAddress.parseNumericAddress(address)); + } + + /** + * Add a search domain to the DNS resolver. + */ + @NonNull + public Builder addSearchDomain(@NonNull String domain) { + if (mConfig.searchDomains == null) { + mConfig.searchDomains = new ArrayList(); + } + mConfig.searchDomains.add(domain); + return this; + } + + /** + * Allows traffic from the specified address family. + * + * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is + * added to this VPN, then all outgoing traffic of that family is blocked. If any address, + * route or DNS server is added, that family is allowed. + * + * This method allows an address family to be unblocked even without adding an address, + * route or DNS server of that family. Traffic of that family will then typically + * fall-through to the underlying network if it's supported. + * + * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6). + * {@link IllegalArgumentException} is thrown if it's neither. + * + * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + @NonNull + public Builder allowFamily(int family) { + if (family == AF_INET) { + mConfig.allowIPv4 = true; + } else if (family == AF_INET6) { + mConfig.allowIPv6 = true; + } else { + throw new IllegalArgumentException(family + " is neither " + AF_INET + " nor " + + AF_INET6); + } + return this; + } + + private void verifyApp(String packageName) throws PackageManager.NameNotFoundException { + IPackageManager pm = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + try { + pm.getApplicationInfo(packageName, 0, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Adds an application that's allowed to access the VPN connection. + * + * If this method is called at least once, only applications added through this method (and + * no others) are allowed access. Else (if this method is never called), all applications + * are allowed by default. If some applications are added, other, un-added applications + * will use networking as if the VPN wasn't running. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addDisallowedApplication} has + * already been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws PackageManager.NameNotFoundException If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder addAllowedApplication(@NonNull String packageName) + throws PackageManager.NameNotFoundException { + if (mConfig.disallowedApplications != null) { + throw new UnsupportedOperationException("addDisallowedApplication already called"); + } + verifyApp(packageName); + if (mConfig.allowedApplications == null) { + mConfig.allowedApplications = new ArrayList(); + } + mConfig.allowedApplications.add(packageName); + return this; + } + + /** + * Adds an application that's denied access to the VPN connection. + * + * By default, all applications are allowed access, except for those denied through this + * method. Denied applications will use networking as if the VPN wasn't running. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addAllowedApplication} has already + * been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws PackageManager.NameNotFoundException If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder addDisallowedApplication(@NonNull String packageName) + throws PackageManager.NameNotFoundException { + if (mConfig.allowedApplications != null) { + throw new UnsupportedOperationException("addAllowedApplication already called"); + } + verifyApp(packageName); + if (mConfig.disallowedApplications == null) { + mConfig.disallowedApplications = new ArrayList(); + } + mConfig.disallowedApplications.add(packageName); + return this; + } + + /** + * Allows all apps to bypass this VPN connection. + * + * By default, all traffic from apps is forwarded through the VPN interface and it is not + * possible for apps to side-step the VPN. If this method is called, apps may use methods + * such as {@link ConnectivityManager#bindProcessToNetwork} to instead send/receive + * directly over the underlying network or any other network they have permissions for. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + @NonNull + public Builder allowBypass() { + mConfig.allowBypass = true; + return this; + } + + /** + * Sets the VPN interface's file descriptor to be in blocking/non-blocking mode. + * + * By default, the file descriptor returned by {@link #establish} is non-blocking. + * + * @param blocking True to put the descriptor into blocking mode; false for non-blocking. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder setBlocking(boolean blocking) { + mConfig.blocking = blocking; + return this; + } + + /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + * @see VpnService#setUnderlyingNetworks + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder setUnderlyingNetworks(@Nullable Network[] networks) { + mConfig.underlyingNetworks = networks != null ? networks.clone() : null; + return this; + } + + /** + * Marks the VPN network as metered. A VPN network is classified as metered when the user is + * sensitive to heavy data usage due to monetary costs and/or data limitations. In such + * cases, you should set this to {@code true} so that apps on the system can avoid doing + * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN + * network to inherit its meteredness from its underlying networks. + * + *

VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be + * considered metered by default. + * + * @param isMetered {@code true} if VPN network should be treated as metered regardless of + * underlying network meteredness + * @return this {@link Builder} object to facilitate chaining method calls + * @see #setUnderlyingNetworks(Network[]) + * @see ConnectivityManager#isActiveNetworkMetered() + */ + @NonNull + public Builder setMetered(boolean isMetered) { + mConfig.isMetered = isMetered; + return this; + } + + /** + * Create a VPN interface using the parameters supplied to this + * builder. The interface works on IP packets, and a file descriptor + * is returned for the application to access them. Each read + * retrieves an outgoing packet which was routed to the interface. + * Each write injects an incoming packet just like it was received + * from the interface. The file descriptor is put into non-blocking + * mode by default to avoid blocking Java threads. To use the file + * descriptor completely in native space, see + * {@link ParcelFileDescriptor#detachFd()}. The application MUST + * close the file descriptor when the VPN connection is terminated. + * The VPN interface will be removed and the network will be + * restored by the system automatically. + * + *

To avoid conflicts, there can be only one active VPN interface + * at the same time. Usually network parameters are never changed + * during the lifetime of a VPN connection. It is also common for an + * application to create a new file descriptor after closing the + * previous one. However, it is rare but not impossible to have two + * interfaces while performing a seamless handover. In this case, the + * old interface will be deactivated when the new one is created + * successfully. Both file descriptors are valid but now outgoing + * packets will be routed to the new interface. Therefore, after + * draining the old file descriptor, the application MUST close it + * and start using the new file descriptor. If the new interface + * cannot be created, the existing interface and its file descriptor + * remain untouched. + * + *

An exception will be thrown if the interface cannot be created + * for any reason. However, this method returns {@code null} if the + * application is not prepared or is revoked. This helps solve + * possible race conditions between other VPN applications. + * + * @return {@link ParcelFileDescriptor} of the VPN interface, or + * {@code null} if the application is not prepared. + * @throws IllegalArgumentException if a parameter is not accepted + * by the operating system. + * @throws IllegalStateException if a parameter cannot be applied + * by the operating system. + * @throws SecurityException if the service is not properly declared + * in {@code AndroidManifest.xml}. + * @see VpnService + */ + @Nullable + public ParcelFileDescriptor establish() { + mConfig.addresses = mAddresses; + mConfig.routes = mRoutes; + + try { + return getService().establishVpn(mConfig); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + } +} diff --git a/framework/src/android/net/apf/ApfCapabilities.aidl b/framework/src/android/net/apf/ApfCapabilities.aidl new file mode 100644 index 0000000000..7c4d4c2da4 --- /dev/null +++ b/framework/src/android/net/apf/ApfCapabilities.aidl @@ -0,0 +1,20 @@ +/* +** +** 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.apf; + +@JavaOnlyStableParcelable parcelable ApfCapabilities; \ No newline at end of file diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java new file mode 100644 index 0000000000..bf5b26e278 --- /dev/null +++ b/framework/src/android/net/apf/ApfCapabilities.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 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.apf; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.res.Resources; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.R; + +/** + * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible + * way to drop unwanted network packets to save power. + * + * See documentation at hardware/google/apf/apf.h + * + * This class is immutable. + * @hide + */ +@SystemApi +public final class ApfCapabilities implements Parcelable { + /** + * Version of APF instruction set supported for packet filtering. 0 indicates no support for + * packet filtering using APF programs. + */ + public final int apfVersionSupported; + + /** + * Maximum size of APF program allowed. + */ + public final int maximumApfProgramSize; + + /** + * Format of packets passed to APF filter. Should be one of ARPHRD_* + */ + public final int apfPacketFormat; + + public ApfCapabilities( + int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) { + this.apfVersionSupported = apfVersionSupported; + this.maximumApfProgramSize = maximumApfProgramSize; + this.apfPacketFormat = apfPacketFormat; + } + + private ApfCapabilities(Parcel in) { + apfVersionSupported = in.readInt(); + maximumApfProgramSize = in.readInt(); + apfPacketFormat = in.readInt(); + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(apfVersionSupported); + dest.writeInt(maximumApfProgramSize); + dest.writeInt(apfPacketFormat); + } + + public static final Creator CREATOR = new Creator() { + @Override + public ApfCapabilities createFromParcel(Parcel in) { + return new ApfCapabilities(in); + } + + @Override + public ApfCapabilities[] newArray(int size) { + return new ApfCapabilities[size]; + } + }; + + @NonNull + @Override + public String toString() { + return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(), + apfVersionSupported, maximumApfProgramSize, apfPacketFormat); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ApfCapabilities)) return false; + final ApfCapabilities other = (ApfCapabilities) obj; + return apfVersionSupported == other.apfVersionSupported + && maximumApfProgramSize == other.maximumApfProgramSize + && apfPacketFormat == other.apfPacketFormat; + } + + /** + * Determines whether the APF interpreter advertises support for the data buffer access opcodes + * LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and + * STDW (STore Data Word) support is present from APFv4 on. + * + * @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported. + */ + public boolean hasDataAccess() { + return apfVersionSupported >= 4; + } + + /** + * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. + */ + public static boolean getApfDrop8023Frames() { + return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames); + } + + /** + * @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped. + */ + public static @NonNull int[] getApfEtherTypeBlackList() { + return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList); + } +} diff --git a/framework/src/android/net/util/DnsUtils.java b/framework/src/android/net/util/DnsUtils.java new file mode 100644 index 0000000000..7908353eed --- /dev/null +++ b/framework/src/android/net/util/DnsUtils.java @@ -0,0 +1,379 @@ +/* + * 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.util; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; +import android.net.Network; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import com.android.internal.util.BitUtils; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @hide + */ +public class DnsUtils { + private static final String TAG = "DnsUtils"; + private static final int CHAR_BIT = 8; + public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01; + public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02; + public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05; + public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e; + private static final Comparator sRfc6724Comparator = new Rfc6724Comparator(); + + /** + * Comparator to sort SortableAddress in Rfc6724 style. + */ + public static class Rfc6724Comparator implements Comparator { + // This function matches the behaviour of _rfc6724_compare in the native resolver. + @Override + public int compare(SortableAddress span1, SortableAddress span2) { + // Rule 1: Avoid unusable destinations. + if (span1.hasSrcAddr != span2.hasSrcAddr) { + return span2.hasSrcAddr - span1.hasSrcAddr; + } + + // Rule 2: Prefer matching scope. + if (span1.scopeMatch != span2.scopeMatch) { + return span2.scopeMatch - span1.scopeMatch; + } + + // TODO: Implement rule 3: Avoid deprecated addresses. + // TODO: Implement rule 4: Prefer home addresses. + + // Rule 5: Prefer matching label. + if (span1.labelMatch != span2.labelMatch) { + return span2.labelMatch - span1.labelMatch; + } + + // Rule 6: Prefer higher precedence. + if (span1.precedence != span2.precedence) { + return span2.precedence - span1.precedence; + } + + // TODO: Implement rule 7: Prefer native transport. + + // Rule 8: Prefer smaller scope. + if (span1.scope != span2.scope) { + return span1.scope - span2.scope; + } + + // Rule 9: Use longest matching prefix. IPv6 only. + if (span1.prefixMatchLen != span2.prefixMatchLen) { + return span2.prefixMatchLen - span1.prefixMatchLen; + } + + // Rule 10: Leave the order unchanged. Collections.sort is a stable sort. + return 0; + } + } + + /** + * Class used to sort with RFC 6724 + */ + public static class SortableAddress { + public final int label; + public final int labelMatch; + public final int scope; + public final int scopeMatch; + public final int precedence; + public final int prefixMatchLen; + public final int hasSrcAddr; + public final InetAddress address; + + public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) { + address = addr; + hasSrcAddr = (srcAddr != null) ? 1 : 0; + label = findLabel(addr); + scope = findScope(addr); + precedence = findPrecedence(addr); + labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0; + scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0; + if (isIpv6Address(addr) && isIpv6Address(srcAddr)) { + prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr); + } else { + prefixMatchLen = 0; + } + } + } + + /** + * Sort the given address list in RFC6724 order. + * Will leave the list unchanged if an error occurs. + * + * This function matches the behaviour of _rfc6724_sort in the native resolver. + */ + public static @NonNull List rfc6724Sort(@Nullable Network network, + @NonNull List answers) { + final ArrayList sortableAnswerList = new ArrayList<>(); + for (InetAddress addr : answers) { + sortableAnswerList.add(new SortableAddress(addr, findSrcAddress(network, addr))); + } + + Collections.sort(sortableAnswerList, sRfc6724Comparator); + + final List sortedAnswers = new ArrayList<>(); + for (SortableAddress ans : sortableAnswerList) { + sortedAnswers.add(ans.address); + } + + return sortedAnswers; + } + + private static @Nullable InetAddress findSrcAddress(@Nullable Network network, + @NonNull InetAddress addr) { + final int domain; + if (isIpv4Address(addr)) { + domain = AF_INET; + } else if (isIpv6Address(addr)) { + domain = AF_INET6; + } else { + return null; + } + final FileDescriptor socket; + try { + socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP); + } catch (ErrnoException e) { + Log.e(TAG, "findSrcAddress:" + e.toString()); + return null; + } + try { + if (network != null) network.bindSocket(socket); + Os.connect(socket, new InetSocketAddress(addr, 0)); + return ((InetSocketAddress) Os.getsockname(socket)).getAddress(); + } catch (IOException | ErrnoException e) { + return null; + } finally { + IoUtils.closeQuietly(socket); + } + } + + /** + * Get the label for a given IPv4/IPv6 address. + * RFC 6724, section 2.1. + * + * Note that Java will return an IPv4-mapped address as an IPv4 address. + */ + private static int findLabel(@NonNull InetAddress addr) { + if (isIpv4Address(addr)) { + return 4; + } else if (isIpv6Address(addr)) { + if (addr.isLoopbackAddress()) { + return 0; + } else if (isIpv6Address6To4(addr)) { + return 2; + } else if (isIpv6AddressTeredo(addr)) { + return 5; + } else if (isIpv6AddressULA(addr)) { + return 13; + } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) { + return 3; + } else if (addr.isSiteLocalAddress()) { + return 11; + } else if (isIpv6Address6Bone(addr)) { + return 12; + } else { + // All other IPv6 addresses, including global unicast addresses. + return 1; + } + } else { + // This should never happen. + return 1; + } + } + + private static boolean isIpv6Address(@Nullable InetAddress addr) { + return addr instanceof Inet6Address; + } + + private static boolean isIpv4Address(@Nullable InetAddress addr) { + return addr instanceof Inet4Address; + } + + private static boolean isIpv6Address6To4(@NonNull InetAddress addr) { + if (!isIpv6Address(addr)) return false; + final byte[] byteAddr = addr.getAddress(); + return byteAddr[0] == 0x20 && byteAddr[1] == 0x02; + } + + private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) { + if (!isIpv6Address(addr)) return false; + final byte[] byteAddr = addr.getAddress(); + return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00 + && byteAddr[3] == 0x00; + } + + private static boolean isIpv6AddressULA(@NonNull InetAddress addr) { + return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc; + } + + private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) { + if (!isIpv6Address(addr)) return false; + final byte[] byteAddr = addr.getAddress(); + return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe; + } + + private static int getIpv6MulticastScope(@NonNull InetAddress addr) { + return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f); + } + + private static int findScope(@NonNull InetAddress addr) { + if (isIpv6Address(addr)) { + if (addr.isMulticastAddress()) { + return getIpv6MulticastScope(addr); + } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) { + /** + * RFC 4291 section 2.5.3 says loopback is to be treated as having + * link-local scope. + */ + return IPV6_ADDR_SCOPE_LINKLOCAL; + } else if (addr.isSiteLocalAddress()) { + return IPV6_ADDR_SCOPE_SITELOCAL; + } else { + return IPV6_ADDR_SCOPE_GLOBAL; + } + } else if (isIpv4Address(addr)) { + if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) { + return IPV6_ADDR_SCOPE_LINKLOCAL; + } else { + /** + * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses + * and shared addresses (100.64.0.0/10), are assigned global scope. + */ + return IPV6_ADDR_SCOPE_GLOBAL; + } + } else { + /** + * This should never happen. + * Return a scope with low priority as a last resort. + */ + return IPV6_ADDR_SCOPE_NODELOCAL; + } + } + + /** + * Get the precedence for a given IPv4/IPv6 address. + * RFC 6724, section 2.1. + * + * Note that Java will return an IPv4-mapped address as an IPv4 address. + */ + private static int findPrecedence(@NonNull InetAddress addr) { + if (isIpv4Address(addr)) { + return 35; + } else if (isIpv6Address(addr)) { + if (addr.isLoopbackAddress()) { + return 50; + } else if (isIpv6Address6To4(addr)) { + return 30; + } else if (isIpv6AddressTeredo(addr)) { + return 5; + } else if (isIpv6AddressULA(addr)) { + return 3; + } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress() + || isIpv6Address6Bone(addr)) { + return 1; + } else { + // All other IPv6 addresses, including global unicast addresses. + return 40; + } + } else { + return 1; + } + } + + /** + * Find number of matching initial bits between the two addresses. + */ + private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr, + @NonNull InetAddress dstAddr) { + final byte[] srcByte = srcAddr.getAddress(); + final byte[] dstByte = dstAddr.getAddress(); + + // This should never happen. + if (srcByte.length != dstByte.length) return 0; + + for (int i = 0; i < dstByte.length; ++i) { + if (srcByte[i] == dstByte[i]) { + continue; + } + int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]); + return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24); // Java ints are 32 bits + } + return dstByte.length * CHAR_BIT; + } + + /** + * Check if given network has Ipv4 capability + * This function matches the behaviour of have_ipv4 in the native resolver. + */ + public static boolean haveIpv4(@Nullable Network network) { + final SocketAddress addrIpv4 = + new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0); + return checkConnectivity(network, AF_INET, addrIpv4); + } + + /** + * Check if given network has Ipv6 capability + * This function matches the behaviour of have_ipv6 in the native resolver. + */ + public static boolean haveIpv6(@Nullable Network network) { + final SocketAddress addrIpv6 = + new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0); + return checkConnectivity(network, AF_INET6, addrIpv6); + } + + private static boolean checkConnectivity(@Nullable Network network, + int domain, @NonNull SocketAddress addr) { + final FileDescriptor socket; + try { + socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP); + } catch (ErrnoException e) { + return false; + } + try { + if (network != null) network.bindSocket(socket); + Os.connect(socket, addr); + } catch (IOException | ErrnoException e) { + return false; + } finally { + IoUtils.closeQuietly(socket); + } + return true; + } +} diff --git a/framework/src/android/net/util/KeepaliveUtils.java b/framework/src/android/net/util/KeepaliveUtils.java new file mode 100644 index 0000000000..bfc4563fbf --- /dev/null +++ b/framework/src/android/net/util/KeepaliveUtils.java @@ -0,0 +1,115 @@ +/* + * 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.util; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.res.Resources; +import android.net.NetworkCapabilities; +import android.text.TextUtils; +import android.util.AndroidRuntimeException; + +import com.android.internal.R; + +/** + * Collection of utilities for socket keepalive offload. + * + * @hide + */ +public final class KeepaliveUtils { + + public static final String TAG = "KeepaliveUtils"; + + public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException { + public KeepaliveDeviceConfigurationException(final String msg) { + super(msg); + } + } + + /** + * Read supported keepalive count for each transport type from overlay resource. This should be + * used to create a local variable store of resource customization, and use it as the input for + * {@link getSupportedKeepalivesForNetworkCapabilities}. + * + * @param context The context to read resource from. + * @return An array of supported keepalive count for each transport type. + */ + @NonNull + public static int[] getSupportedKeepalives(@NonNull Context context) { + String[] res = null; + try { + res = context.getResources().getStringArray( + R.array.config_networkSupportedKeepaliveCount); + } catch (Resources.NotFoundException unused) { + } + if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource"); + + final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1]; + for (final String row : res) { + if (TextUtils.isEmpty(row)) { + throw new KeepaliveDeviceConfigurationException("Empty string"); + } + final String[] arr = row.split(","); + if (arr.length != 2) { + throw new KeepaliveDeviceConfigurationException("Invalid parameter length"); + } + + int transport; + int supported; + try { + transport = Integer.parseInt(arr[0]); + supported = Integer.parseInt(arr[1]); + } catch (NumberFormatException e) { + throw new KeepaliveDeviceConfigurationException("Invalid number format"); + } + + if (!NetworkCapabilities.isValidTransport(transport)) { + throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport); + } + + if (supported < 0) { + throw new KeepaliveDeviceConfigurationException( + "Invalid supported count " + supported + " for " + + NetworkCapabilities.transportNameOf(transport)); + } + ret[transport] = supported; + } + return ret; + } + + /** + * Get supported keepalive count for the given {@link NetworkCapabilities}. + * + * @param supportedKeepalives An array of supported keepalive count for each transport type. + * @param nc The {@link NetworkCapabilities} of the network the socket keepalive is on. + * + * @return Supported keepalive count for the given {@link NetworkCapabilities}. + */ + public static int getSupportedKeepalivesForNetworkCapabilities( + @NonNull int[] supportedKeepalives, @NonNull NetworkCapabilities nc) { + final int[] transports = nc.getTransportTypes(); + if (transports.length == 0) return 0; + int supportedCount = supportedKeepalives[transports[0]]; + // Iterate through transports and return minimum supported value. + for (final int transport : transports) { + if (supportedCount > supportedKeepalives[transport]) { + supportedCount = supportedKeepalives[transport]; + } + } + return supportedCount; + } +} diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java new file mode 100644 index 0000000000..85e3fa3048 --- /dev/null +++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 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.util; + +import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; +import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.List; + +/** + * A class to encapsulate management of the "Smart Networking" capability of + * avoiding bad Wi-Fi when, for example upstream connectivity is lost or + * certain critical link failures occur. + * + * This enables the device to switch to another form of connectivity, like + * mobile, if it's available and working. + * + * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied + * Handler' whenever the computed "avoid bad wifi" value changes. + * + * Disabling this reverts the device to a level of networking sophistication + * circa 2012-13 by disabling disparate code paths each of which contribute to + * maintaining continuous, working Internet connectivity. + * + * @hide + */ +public class MultinetworkPolicyTracker { + private static String TAG = MultinetworkPolicyTracker.class.getSimpleName(); + + private final Context mContext; + private final Handler mHandler; + private final Runnable mAvoidBadWifiCallback; + private final List mSettingsUris; + private final ContentResolver mResolver; + private final SettingObserver mSettingObserver; + private final BroadcastReceiver mBroadcastReceiver; + + private volatile boolean mAvoidBadWifi = true; + private volatile int mMeteredMultipathPreference; + private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + public MultinetworkPolicyTracker(Context ctx, Handler handler) { + this(ctx, handler, null); + } + + public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) { + mContext = ctx; + mHandler = handler; + mAvoidBadWifiCallback = avoidBadWifiCallback; + mSettingsUris = Arrays.asList( + Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), + Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); + mResolver = mContext.getContentResolver(); + mSettingObserver = new SettingObserver(); + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reevaluateInternal(); + } + }; + + ctx.getSystemService(TelephonyManager.class).listen( + new PhoneStateListener(handler.getLooper()) { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveSubId = subId; + reevaluateInternal(); + } + }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + + updateAvoidBadWifi(); + updateMeteredMultipathPreference(); + } + + public void start() { + for (Uri uri : mSettingsUris) { + mResolver.registerContentObserver(uri, false, mSettingObserver); + } + + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter, + null /* broadcastPermission */, mHandler); + + reevaluate(); + } + + public void shutdown() { + mResolver.unregisterContentObserver(mSettingObserver); + + mContext.unregisterReceiver(mBroadcastReceiver); + } + + public boolean getAvoidBadWifi() { + return mAvoidBadWifi; + } + + // TODO: move this to MultipathPolicyTracker. + public int getMeteredMultipathPreference() { + return mMeteredMultipathPreference; + } + + /** + * Whether the device or carrier configuration disables avoiding bad wifi by default. + */ + public boolean configRestrictsAvoidBadWifi() { + return (getResourcesForActiveSubId().getInteger(R.integer.config_networkAvoidBadWifi) == 0); + } + + @NonNull + private Resources getResourcesForActiveSubId() { + return SubscriptionManager.getResourcesForSubId(mContext, mActiveSubId); + } + + /** + * Whether we should display a notification when wifi becomes unvalidated. + */ + public boolean shouldNotifyWifiUnvalidated() { + return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null; + } + + public String getAvoidBadWifiSetting() { + return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI); + } + + @VisibleForTesting + public void reevaluate() { + mHandler.post(this::reevaluateInternal); + } + + /** + * Reevaluate the settings. Must be called on the handler thread. + */ + private void reevaluateInternal() { + if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) { + mAvoidBadWifiCallback.run(); + } + updateMeteredMultipathPreference(); + } + + public boolean updateAvoidBadWifi() { + final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting()); + final boolean prev = mAvoidBadWifi; + mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi(); + return mAvoidBadWifi != prev; + } + + /** + * The default (device and carrier-dependent) value for metered multipath preference. + */ + public int configMeteredMultipathPreference() { + return mContext.getResources().getInteger( + R.integer.config_networkMeteredMultipathPreference); + } + + public void updateMeteredMultipathPreference() { + String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE); + try { + mMeteredMultipathPreference = Integer.parseInt(setting); + } catch (NumberFormatException e) { + mMeteredMultipathPreference = configMeteredMultipathPreference(); + } + } + + private class SettingObserver extends ContentObserver { + public SettingObserver() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + Log.wtf(TAG, "Should never be reached."); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (!mSettingsUris.contains(uri)) { + Log.wtf(TAG, "Unexpected settings observation: " + uri); + } + reevaluate(); + } + } +} diff --git a/framework/src/android/net/util/SocketUtils.java b/framework/src/android/net/util/SocketUtils.java new file mode 100644 index 0000000000..e64060f1b2 --- /dev/null +++ b/framework/src/android/net/util/SocketUtils.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015 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.util; + +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_BINDTODEVICE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.NetworkUtils; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.Os; +import android.system.PacketSocketAddress; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketAddress; + +/** + * Collection of utilities to interact with raw sockets. + * @hide + */ +@SystemApi +public final class SocketUtils { + /** + * Create a raw datagram socket that is bound to an interface. + * + *

Data sent through the socket will go directly to the underlying network, ignoring VPNs. + */ + public static void bindSocketToInterface(@NonNull FileDescriptor socket, @NonNull String iface) + throws ErrnoException { + // SO_BINDTODEVICE actually takes a string. This works because the first member + // of struct ifreq is a NULL-terminated interface name. + // TODO: add a setsockoptString() + Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface); + NetworkUtils.protectFromVpn(socket); + } + + /** + * Make a socket address to communicate with netlink. + */ + @NonNull + public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) { + return new NetlinkSocketAddress(portId, groupsMask); + } + + /** + * Make socket address that packet sockets can bind to. + * + * @param protocol the layer 2 protocol of the packets to receive. One of the {@code ETH_P_*} + * constants in {@link android.system.OsConstants}. + * @param ifIndex the interface index on which packets will be received. + */ + @NonNull + public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex) { + return new PacketSocketAddress( + protocol /* sll_protocol */, + ifIndex /* sll_ifindex */, + null /* sll_addr */); + } + + /** + * Make a socket address that packet socket can send packets to. + * @deprecated Use {@link #makePacketSocketAddress(int, int, byte[])} instead. + * + * @param ifIndex the interface index on which packets will be sent. + * @param hwAddr the hardware address to which packets will be sent. + */ + @Deprecated + @NonNull + public static SocketAddress makePacketSocketAddress(int ifIndex, @NonNull byte[] hwAddr) { + return new PacketSocketAddress( + 0 /* sll_protocol */, + ifIndex /* sll_ifindex */, + hwAddr /* sll_addr */); + } + + /** + * Make a socket address that a packet socket can send packets to. + * + * @param protocol the layer 2 protocol of the packets to send. One of the {@code ETH_P_*} + * constants in {@link android.system.OsConstants}. + * @param ifIndex the interface index on which packets will be sent. + * @param hwAddr the hardware address to which packets will be sent. + */ + @NonNull + public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex, + @NonNull byte[] hwAddr) { + return new PacketSocketAddress( + protocol /* sll_protocol */, + ifIndex /* sll_ifindex */, + hwAddr /* sll_addr */); + } + + /** + * @see IoBridge#closeAndSignalBlockedThreads(FileDescriptor) + */ + public static void closeSocket(@Nullable FileDescriptor fd) throws IOException { + IoBridge.closeAndSignalBlockedThreads(fd); + } + + private SocketUtils() {} +}