diff options
128 files changed, 8687 insertions, 4425 deletions
diff --git a/Android.bp b/Android.bp index 416613d03e79..45b6511fecdf 100644 --- a/Android.bp +++ b/Android.bp @@ -676,6 +676,8 @@ java_defaults { "ext", ], + jarjar_rules: ":framework-hidl-jarjar", + static_libs: [ "apex_aidl_interface-java", "networkstack-aidl-interfaces-java", @@ -730,6 +732,11 @@ filegroup { ], } +filegroup { + name: "framework-hidl-jarjar", + srcs: ["jarjar_rules_hidl.txt"], +} + java_library { name: "framework", defaults: ["framework-defaults"], @@ -740,11 +747,7 @@ java_library { name: "framework-annotation-proc", defaults: ["framework-defaults"], // Use UsedByApps annotation processor - annotation_processors: ["unsupportedappusage-annotation-processor"], - // b/25860419: annotation processors must be explicitly specified for grok - annotation_processor_classes: [ - "android.processor.unsupportedappusage.UsedByAppsProcessor", - ], + plugins: ["unsupportedappusage-annotation-processor"], } // A host library including just UnsupportedAppUsage.java so that the annotation diff --git a/api/current.txt b/api/current.txt index 7ea15a5c014f..37dffa80e235 100755 --- a/api/current.txt +++ b/api/current.txt @@ -8385,6 +8385,13 @@ package android.bluetooth { method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int); } + public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile { + method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method public int getConnectionState(android.bluetooth.BluetoothDevice); + method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); + field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; + } + public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile { method public boolean connect(android.bluetooth.BluetoothDevice); method public boolean disconnect(android.bluetooth.BluetoothDevice); @@ -8480,6 +8487,7 @@ package android.bluetooth { field public static final int GATT_SERVER = 8; // 0x8 field public static final int HEADSET = 1; // 0x1 field @Deprecated public static final int HEALTH = 3; // 0x3 + field public static final int HEARING_AID = 21; // 0x15 field public static final int HID_DEVICE = 19; // 0x13 field public static final int SAP = 10; // 0xa field public static final int STATE_CONNECTED = 2; // 0x2 @@ -11356,6 +11364,7 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; + field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television"; field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen"; @@ -27103,6 +27112,7 @@ package android.net { public class ConnectivityManager { method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); method public boolean bindProcessToNetwork(@Nullable android.net.Network); + method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); @@ -27212,6 +27222,28 @@ package android.net { field public int serverAddress; } + public final class DnsResolver { + method public static android.net.DnsResolver getInstance(); + method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException; + field public static final int CLASS_IN = 1; // 0x1 + field public static final int FLAG_EMPTY = 0; // 0x0 + field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 + field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 + field public static final int FLAG_NO_RETRY = 1; // 0x1 + field public static final int TYPE_A = 1; // 0x1 + field public static final int TYPE_AAAA = 28; // 0x1c + } + + public static interface DnsResolver.InetAddressAnswerListener { + method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>); + } + + public static interface DnsResolver.RawAnswerListener { + method public void onAnswer(@Nullable byte[]); + } + public class InetAddresses { method public static boolean isNumericAddress(String); method public static java.net.InetAddress parseNumericAddress(String); @@ -27585,6 +27617,29 @@ package android.net { ctor public SSLSessionCache(android.content.Context); } + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + method public final void close(); + method public final void start(@IntRange(from=0xa, to=0xe10) int); + method public final void stop(); + field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 + field public static final int ERROR_HARDWARE_UNSUPPORTED = -30; // 0xffffffe2 + field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 + field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 + } + + public static class SocketKeepalive.Callback { + ctor public SocketKeepalive.Callback(); + method public void onDataReceived(); + method public void onError(int); + method public void onStarted(); + method public void onStopped(); + } + public class TrafficStats { ctor public TrafficStats(); method public static void clearThreadStatsTag(); @@ -37338,6 +37393,7 @@ package android.provider { field public static final String CONTENT_ID = "cid"; field public static final String CONTENT_LOCATION = "cl"; field public static final String CONTENT_TYPE = "ct"; + field public static final android.net.Uri CONTENT_URI; field public static final String CT_START = "ctt_s"; field public static final String CT_TYPE = "ctt_t"; field public static final String FILENAME = "fn"; @@ -42369,6 +42425,7 @@ package android.telephony { public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable { method public int describeContents(); method public int getAsuLevel(); + method public int getBitErrorRate(); method public int getDbm(); method public int getLevel(); method public int getTimingAdvance(); @@ -42670,16 +42727,16 @@ package android.telephony { public class SignalStrength implements android.os.Parcelable { method public int describeContents(); - method public int getCdmaDbm(); - method public int getCdmaEcio(); + method @Deprecated public int getCdmaDbm(); + method @Deprecated public int getCdmaEcio(); method @NonNull public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths(); - method public int getEvdoDbm(); - method public int getEvdoEcio(); - method public int getEvdoSnr(); - method public int getGsmBitErrorRate(); - method public int getGsmSignalStrength(); + method @Deprecated public int getEvdoDbm(); + method @Deprecated public int getEvdoEcio(); + method @Deprecated public int getEvdoSnr(); + method @Deprecated public int getGsmBitErrorRate(); + method @Deprecated public int getGsmSignalStrength(); method public int getLevel(); - method public boolean isGsm(); + method @Deprecated public boolean isGsm(); method public void writeToParcel(android.os.Parcel, int); field public static final int INVALID = 2147483647; // 0x7fffffff } @@ -42831,6 +42888,7 @@ package android.telephony { method public String getNumber(); method public int getSimSlotIndex(); method public int getSubscriptionId(); + method public int getSubscriptionType(); method public boolean isEmbedded(); method public boolean isOpportunistic(); method public void writeToParcel(android.os.Parcel, int); @@ -42881,6 +42939,8 @@ package android.telephony { field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff + field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 + field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 } public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener { diff --git a/api/system-current.txt b/api/system-current.txt index 456a5c2d0ae1..8ce317fab37a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -735,7 +735,7 @@ package android.bluetooth { field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; } - public abstract class BluetoothAdapter.MetadataListener { + public abstract static class BluetoothAdapter.MetadataListener { ctor public BluetoothAdapter.MetadataListener(); method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String); } @@ -3060,6 +3060,7 @@ package android.net { } public class ConnectivityManager { + method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 88957df87d08..10cbc077fd4e 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -493,6 +493,7 @@ const std::map<string, uint32_t> UidMap::sAidToUidMapping = {{"AID_ROOT", 0}, {"AID_LMKD", 1069}, {"AID_LLKD", 1070}, {"AID_IORAPD", 1071}, + {"AID_NETWORK_STACK", 1073}, {"AID_SHELL", 2000}, {"AID_CACHE", 2001}, {"AID_DIAG", 2002}}; diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index a140db0fd9f6..3d5b32a62ca5 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -1896,22 +1896,10 @@ Lcom/android/internal/os/RuntimeInit;->getApplicationObject()Landroid/os/IBinder Lcom/android/internal/os/RuntimeInit;->initialized:Z Lcom/android/internal/os/RuntimeInit;->main([Ljava/lang/String;)V Lcom/android/internal/os/RuntimeInit;->mApplicationObject:Landroid/os/IBinder; -Lcom/android/internal/os/ZygoteConnection$Arguments;-><init>([Ljava/lang/String;)V -Lcom/android/internal/os/ZygoteConnection$Arguments;->effectiveCapabilities:J -Lcom/android/internal/os/ZygoteConnection$Arguments;->gid:I -Lcom/android/internal/os/ZygoteConnection$Arguments;->gids:[I -Lcom/android/internal/os/ZygoteConnection$Arguments;->permittedCapabilities:J -Lcom/android/internal/os/ZygoteConnection$Arguments;->remainingArgs:[Ljava/lang/String; -Lcom/android/internal/os/ZygoteConnection$Arguments;->rlimits:Ljava/util/ArrayList; -Lcom/android/internal/os/ZygoteConnection$Arguments;->uid:I -Lcom/android/internal/os/ZygoteConnection;->applyUidSecurityPolicy(Lcom/android/internal/os/ZygoteConnection$Arguments;Landroid/net/Credentials;)V Lcom/android/internal/os/ZygoteConnection;->closeSocket()V -Lcom/android/internal/os/ZygoteConnection;->getFileDesciptor()Ljava/io/FileDescriptor; -Lcom/android/internal/os/ZygoteConnection;->intArray2d:[[I Lcom/android/internal/os/ZygoteConnection;->mSocket:Landroid/net/LocalSocket; Lcom/android/internal/os/ZygoteConnection;->mSocketOutStream:Ljava/io/DataOutputStream; Lcom/android/internal/os/ZygoteConnection;->peer:Landroid/net/Credentials; -Lcom/android/internal/os/ZygoteConnection;->readArgumentList()[Ljava/lang/String; Lcom/android/internal/os/ZygoteInit;->main([Ljava/lang/String;)V Lcom/android/internal/os/ZygoteInit;->mResources:Landroid/content/res/Resources; Lcom/android/internal/os/ZygoteSecurityException;-><init>(Ljava/lang/String;)V @@ -3624,7 +3612,7 @@ Lcom/android/internal/telephony/SubscriptionController;->mDefaultPhoneId:I Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object; Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V -Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V +Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index e04aac49cfb5..4afd520c99c5 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1900,6 +1900,20 @@ public final class BluetoothAdapter { } /** + * Return true if Hearing Aid Profile is supported. + * + * @return true if phone supports Hearing Aid Profile + */ + private boolean isHearingAidProfileSupported() { + try { + return mManagerService.isHearingAidProfileSupported(); + } catch (RemoteException e) { + Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e); + return false; + } + } + + /** * Get the maximum number of connected audio devices. * * @return the maximum number of connected audio devices @@ -2051,6 +2065,11 @@ public final class BluetoothAdapter { supportedProfiles.add(i); } } + } else { + // Bluetooth is disabled. Just fill in known supported Profiles + if (isHearingAidProfileSupported()) { + supportedProfiles.add(BluetoothProfile.HEARING_AID); + } } } } catch (RemoteException e) { @@ -2468,15 +2487,16 @@ public final class BluetoothAdapter { * Get the profile proxy object associated with the profile. * * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must - * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status - * and to get the proxy object. + * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link + * BluetoothProfile#GATT_SERVER}. Clients must implement {@link + * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the + * proxy object. * * @param context Context of the application * @param listener The service Listener for connection callbacks. * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET}, - * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or - * {@link BluetoothProfile#GATT_SERVER}. + * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link + * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}. * @return true on success, false on error */ public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, @@ -2525,8 +2545,11 @@ public final class BluetoothAdapter { BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener); return true; } else if (profile == BluetoothProfile.HEARING_AID) { - BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener); - return true; + if (isHearingAidProfileSupported()) { + BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener); + return true; + } + return false; } else { return false; } @@ -3253,7 +3276,7 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - public abstract class MetadataListener { + public abstract static class MetadataListener { /** * Callback triggered if the metadata of {@link BluetoothDevice} registered in * {@link #registerMetadataListener}. diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java index 2bf7daddcb2c..23a8159de4c3 100644 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ b/core/java/android/bluetooth/BluetoothHearingAid.java @@ -38,15 +38,14 @@ import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * This class provides the public APIs to control the Bluetooth Hearing Aid - * profile. + * This class provides the public APIs to control the Hearing Aid profile. * * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get * the BluetoothHearingAid proxy object. * - * <p> Each method is protected with its appropriate permission. - * @hide + * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each + * method is protected with its appropriate permission. */ public final class BluetoothHearingAid implements BluetoothProfile { private static final String TAG = "BluetoothHearingAid"; @@ -55,7 +54,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { /** * Intent used to broadcast the change in connection state of the Hearing Aid - * profile. + * profile. Please note that in the binaural case, there will be two different LE devices for + * the left and right side and each device will have their own connection state changes.S * * <p>This intent will have 3 extras: * <ul> @@ -76,27 +76,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; /** - * Intent used to broadcast the change in the Playing state of the Hearing Aid - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PLAYING_STATE_CHANGED = - "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED"; - - /** * Intent used to broadcast the selection of a connected device as active. * * <p>This intent will have one extra: @@ -107,6 +86,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. + * + * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage @@ -114,32 +95,38 @@ public final class BluetoothHearingAid implements BluetoothProfile { "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED"; /** - * Hearing Aid device is streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + * This device represents Left Hearing Aid. + * + * @hide */ - public static final int STATE_PLAYING = 10; + public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; /** - * Hearing Aid device is NOT streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. + * This device represents Right Hearing Aid. + * + * @hide */ - public static final int STATE_NOT_PLAYING = 11; - - /** This device represents Left Hearing Aid. */ - public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; - - /** This device represents Right Hearing Aid. */ public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT; - /** This device is Monaural. */ + /** + * This device is Monaural. + * + * @hide + */ public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL; - /** This device is Binaural (should receive only left or right audio). */ + /** + * This device is Binaural (should receive only left or right audio). + * + * @hide + */ public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL; - /** Can't read ClientID for this device */ + /** + * Indicates the HiSyncID could not be read and is unavailable. + * + * @hide + */ public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID; private Context mContext; @@ -235,12 +222,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { } } - @Override - public void finalize() { - // The empty finalize needs to be kept or the - // cts signature tests would fail. - } - /** * Initiate connection to a profile of the remote bluetooth device. * @@ -537,10 +518,6 @@ public final class BluetoothHearingAid implements BluetoothProfile { return "connected"; case STATE_DISCONNECTING: return "disconnecting"; - case STATE_PLAYING: - return "playing"; - case STATE_NOT_PLAYING: - return "not playing"; default: return "<unknown state " + state + ">"; } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 3c87c739e1f6..b8670dbeadad 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -185,7 +185,6 @@ public interface BluetoothProfile { /** * Hearing Aid Device * - * @hide */ int HEARING_AID = 21; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 83e8785f29f1..2130c39a8905 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2188,6 +2188,13 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports attaching to IMS implementations using the ImsService API in telephony. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports connecting to USB devices * as the USB host. diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index f30b3fee7f46..755232c6a6b6 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -628,4 +628,9 @@ public abstract class PackageManagerInternal { */ public abstract boolean hasSignatureCapability(int serverUid, int clientUid, @PackageParser.SigningDetails.CertCapabilities int capability); + + /** + * Ask the package manager to compile layouts in the given package. + */ + public abstract boolean compileLayouts(String packageName); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c809ccad5907..5bb24bab6e48 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -15,6 +15,9 @@ */ package android.net; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; + +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +31,8 @@ import android.annotation.UnsupportedAppUsage; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.SocketKeepalive.Callback; import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -58,6 +63,7 @@ import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; +import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; @@ -66,6 +72,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /** * Class that answers queries about the state of network connectivity. It also @@ -1699,6 +1706,8 @@ public class ConnectivityManager { * {@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 { @@ -1802,6 +1811,8 @@ public class ConnectivityManager { /** * Starts an IPsec NAT-T keepalive packet with the specified parameters. * + * @deprecated Use {@link #createSocketKeepalive} instead. + * * @hide */ @UnsupportedAppUsage @@ -1821,6 +1832,62 @@ public class ConnectivityManager { } /** + * 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, which can be used to control this keepalive object. + **/ + public SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull UdpEncapsulationSocket socket, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(), + 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 fd The {@link FileDescriptor} that needs to be kept alive. The provided + * {@link FileDescriptor} 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. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public SocketKeepalive createNattKeepalive(@NonNull Network network, + @NonNull FileDescriptor fd, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */, + source, destination, 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. diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java new file mode 100644 index 000000000000..458fb340b196 --- /dev/null +++ b/core/java/android/net/DnsPacket.java @@ -0,0 +1,235 @@ +/* + * 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.text.TextUtils; + +import com.android.internal.util.BitUtils; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +/** + * Defines basic data for DNS protocol based on RFC 1035. + * Subclasses create the specific format used in DNS packet. + * + * @hide + */ +public abstract class DnsPacket { + public class DnsHeader { + private static final String TAG = "DnsHeader"; + public final int id; + public final int flags; + public final int rcode; + private final int[] mSectionCount; + + /** + * Create a new DnsHeader from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS header. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { + id = BitUtils.uint16(buf.getShort()); + flags = BitUtils.uint16(buf.getShort()); + rcode = flags & 0xF; + mSectionCount = new int[NUM_SECTIONS]; + for (int i = 0; i < NUM_SECTIONS; ++i) { + mSectionCount[i] = BitUtils.uint16(buf.getShort()); + } + } + + /** + * Get section count by section type. + */ + public int getSectionCount(int sectionType) { + return mSectionCount[sectionType]; + } + } + + public class DnsSection { + private static final int MAXNAMESIZE = 255; + private static final int MAXLABELSIZE = 63; + private static final int MAXLABELCOUNT = 128; + private static final int NAME_NORMAL = 0; + private static final int NAME_COMPRESSION = 0xC0; + private final DecimalFormat byteFormat = new DecimalFormat(); + private final FieldPosition pos = new FieldPosition(0); + + private static final String TAG = "DnsSection"; + + public final String dName; + public final int nsType; + public final int nsClass; + public final long ttl; + private final byte[] mRR; + + /** + * Create a new DnsSection from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS section. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsSection(int sectionType, @NonNull ByteBuffer buf) + throws BufferUnderflowException, ParseException { + dName = parseName(buf, 0 /* Parse depth */); + if (dName.length() > MAXNAMESIZE) { + throw new ParseException("Parse name fail, name size is too long"); + } + nsType = BitUtils.uint16(buf.getShort()); + nsClass = BitUtils.uint16(buf.getShort()); + + if (sectionType != QDSECTION) { + ttl = BitUtils.uint32(buf.getInt()); + final int length = BitUtils.uint16(buf.getShort()); + mRR = new byte[length]; + buf.get(mRR); + } else { + ttl = 0; + mRR = null; + } + } + + /** + * Get a copy of rr. + */ + @Nullable public byte[] getRR() { + return (mRR == null) ? null : mRR.clone(); + } + + /** + * Convert label from {@code byte[]} to {@code String} + * + * It follows the same converting rule as native layer. + * (See ns_name.c in libc) + * + */ + private String labelToString(@NonNull byte[] label) { + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < label.length; ++i) { + int b = BitUtils.uint8(label[i]); + // Control characters and non-ASCII characters. + if (b <= 0x20 || b >= 0x7f) { + sb.append('\\'); + byteFormat.format(b, sb, pos); + } else if (b == '"' || b == '.' || b == ';' || b == '\\' + || b == '(' || b == ')' || b == '@' || b == '$') { + sb.append('\\'); + sb.append((char) b); + } else { + sb.append((char) b); + } + } + return sb.toString(); + } + + private String parseName(@NonNull ByteBuffer buf, int depth) throws + BufferUnderflowException, ParseException { + if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels"); + final int len = BitUtils.uint8(buf.get()); + final int mask = len & NAME_COMPRESSION; + if (0 == len) { + return ""; + } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) { + throw new ParseException("Parse name fail, bad label type"); + } else if (mask == NAME_COMPRESSION) { + // Name compression based on RFC 1035 - 4.1.4 Message compression + final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get()); + final int oldPos = buf.position(); + if (offset >= oldPos - 2) { + throw new ParseException("Parse compression name fail, invalid compression"); + } + buf.position(offset); + final String pointed = parseName(buf, depth + 1); + buf.position(oldPos); + return pointed; + } else { + final byte[] label = new byte[len]; + buf.get(label); + final String head = labelToString(label); + if (head.length() > MAXLABELSIZE) { + throw new ParseException("Parse name fail, invalid label length"); + } + final String tail = parseName(buf, depth + 1); + return TextUtils.isEmpty(tail) ? head : head + "." + tail; + } + } + } + + public static final int QDSECTION = 0; + public static final int ANSECTION = 1; + public static final int NSSECTION = 2; + public static final int ARSECTION = 3; + private static final int NUM_SECTIONS = ARSECTION + 1; + + private static final String TAG = DnsPacket.class.getSimpleName(); + + protected final DnsHeader mHeader; + protected final List<DnsSection>[] mSections; + + public static class ParseException extends Exception { + public ParseException(String msg) { + super(msg); + } + + public ParseException(String msg, Throwable cause) { + super(msg, cause); + } + } + + protected DnsPacket(@NonNull byte[] data) throws ParseException { + if (null == data) throw new ParseException("Parse header failed, null input data"); + final ByteBuffer buffer; + try { + buffer = ByteBuffer.wrap(data); + mHeader = new DnsHeader(buffer); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse Header fail, bad input data", e); + } + + mSections = new ArrayList[NUM_SECTIONS]; + + for (int i = 0; i < NUM_SECTIONS; ++i) { + final int count = mHeader.getSectionCount(i); + if (count > 0) { + mSections[i] = new ArrayList(count); + } + for (int j = 0; j < count; ++j) { + try { + mSections[i].add(new DnsSection(i, buffer)); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse section fail", e); + } + } + } + } +} diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java new file mode 100644 index 000000000000..6d54264cd89f --- /dev/null +++ b/core/java/android/net/DnsResolver.java @@ -0,0 +1,289 @@ +/* + * 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.resNetworkQuery; +import static android.net.NetworkUtils.resNetworkResult; +import static android.net.NetworkUtils.resNetworkSend; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.util.Log; + +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.function.Consumer; + + +/** + * Dns resolver class for asynchronous dns querying + * + */ +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; + + @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; + + private static final int DNS_RAW_RESPONSE = 1; + + private static final int NETID_UNSET = 0; + + private static final DnsResolver sInstance = new DnsResolver(); + + /** + * listener for receiving raw answers + */ + public interface RawAnswerListener { + /** + * {@code byte[]} is {@code null} if query timed out + */ + void onAnswer(@Nullable byte[] answer); + } + + /** + * listener for receiving parsed answers + */ + public interface InetAddressAnswerListener { + /** + * Will be called exactly once with all the answers to the query. + * size of addresses will be zero if no available answer could be parsed. + */ + void onAnswer(@NonNull List<InetAddress> addresses); + } + + /** + * Get instance for DnsResolver + */ + public static DnsResolver getInstance() { + return sInstance; + } + + private DnsResolver() {} + + /** + * Pass in a blob and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param query blob message + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkSend((network != null + ? network.netId : NETID_UNSET), query, query.length, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @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 handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass, + @QueryType int nsType, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get back a set of InetAddresses asynchronously. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link InetAddressAnswerListener} will be invoked. + * @param listener an {@link InetAddressAnswerListener} 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 Handler handler, @NonNull InetAddressAnswerListener listener) + throws ErrnoException { + final FileDescriptor v4fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags); + final FileDescriptor v6fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags); + + final InetAddressAnswerAccumulator accmulator = + new InetAddressAnswerAccumulator(2, listener); + final Consumer<byte[]> consumer = answerbuf -> + accmulator.accumulate(parseAnswers(answerbuf)); + + registerFDListener(handler.getLooper().getQueue(), v4fd, consumer); + registerFDListener(handler.getLooper().getQueue(), v6fd, consumer); + } + + private void registerFDListener(@NonNull MessageQueue queue, + @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) { + queue.addOnFileDescriptorEventListener( + queryfd, + FD_EVENTS, + (fd, events) -> { + byte[] answerbuf = null; + try { + // TODO: Implement result function in Java side instead of using JNI + // Because JNI method close fd prior than unregistering fd on + // event listener. + answerbuf = resNetworkResult(fd); + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + } + answerConsumer.accept(answerbuf); + + // Unregister this fd listener + return 0; + }); + } + + private 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.rcode != 0) { + throw new ParseException("Response error, rcode:" + mHeader.rcode); + } + if (mHeader.getSectionCount(ANSECTION) == 0) { + throw new ParseException("No available answer"); + } + if (mHeader.getSectionCount(QDSECTION) == 0) { + throw new ParseException("No question found"); + } + // Assume only one question per answer packet. (RFC1035) + mQueryType = mSections[QDSECTION].get(0).nsType; + } + + public @NonNull List<InetAddress> getAddresses() { + final List<InetAddress> results = new ArrayList<InetAddress>(); + for (final DnsSection ansSec : mSections[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; + } + } + + private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) { + try { + return (data == null) ? null : new DnsAddressAnswer(data).getAddresses(); + } catch (DnsPacket.ParseException e) { + Log.e(TAG, "Parse answer fail " + e.getMessage()); + return null; + } + } + + private class InetAddressAnswerAccumulator { + private final List<InetAddress> mAllAnswers; + private final InetAddressAnswerListener mAnswerListener; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) { + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mAnswerListener = listener; + } + + public void accumulate(@Nullable List<InetAddress> answer) { + if (null != answer) { + mAllAnswers.addAll(answer); + } + if (++mReceivedAnswerCount == mTargetAnswerCount) { + mAnswerListener.onAnswer(mAllAnswers); + } + } + } +} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 131925ec28e9..e97060a0a599 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -181,6 +181,10 @@ interface IConnectivityManager void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, int srcPort, String dstAddr); + void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId, + int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, + String dstAddr); + void stopKeepalive(in Network network, int slot); String getCaptivePortalServerUrl(); diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index b7af37474307..f0fe92eb8641 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -24,7 +24,7 @@ import android.net.RouteInfo; * * @hide */ -interface INetworkManagementEventObserver { +oneway interface INetworkManagementEventObserver { /** * Interface configuration status has changed. * diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl index 2df8ab7ec198..8b64f1c7c45a 100644 --- a/core/java/android/net/INetworkStackConnector.aidl +++ b/core/java/android/net/INetworkStackConnector.aidl @@ -18,10 +18,12 @@ package android.net; import android.net.INetworkMonitorCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; /** @hide */ oneway interface INetworkStackConnector { void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params, in IDhcpServerCallbacks cb); void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb); + void makeIpClient(in String ifName, in IIpClientCallbacks callbacks); }
\ No newline at end of file diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java new file mode 100644 index 000000000000..88631aea3a88 --- /dev/null +++ b/core/java/android/net/NattSocketKeepalive.java @@ -0,0 +1,75 @@ +/* + * 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.Binder; +import android.os.RemoteException; +import android.util.Log; + +import java.io.FileDescriptor; +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; + @NonNull private final FileDescriptor mFd; + private final int mResourceId; + + NattSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull FileDescriptor fd, + int resourceId, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, executor, callback); + mSource = source; + mDestination = destination; + mFd = fd; + mResourceId = resourceId; + } + + @Override + void startImpl(int intervalSec) { + try { + mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger, + new Binder(), mSource.getHostAddress(), mDestination.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + stopLooper(); + } + } + + @Override + void stopImpl() { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + stopLooper(); + } + } +} diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index af043eeccde2..d277034650a1 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; import android.os.Binder; import android.os.IBinder; import android.os.Process; @@ -84,6 +85,21 @@ public class NetworkStack { } /** + * Create an IpClient on the specified interface. + * + * <p>The IpClient will be returned asynchronously through the provided callbacks. + */ + public void makeIpClient(String ifName, IIpClientCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeIpClient(ifName, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + + /** * Create a NetworkMonitor. * * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index c0aa4a6faf12..7f4d8cd1cfcb 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -34,6 +34,8 @@ import java.util.Collection; import java.util.Locale; import java.util.TreeSet; +import android.system.ErrnoException; + /** * Native methods for managing network interfaces. * @@ -133,6 +135,32 @@ public class NetworkUtils { 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 a byte array containing blob answer + */ + public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException; + + /** * Add an entry into the ARP cache. */ public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname, diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java new file mode 100644 index 000000000000..97d50f4bac05 --- /dev/null +++ b/core/java/android/net/SocketKeepalive.java @@ -0,0 +1,224 @@ +/* + * 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.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Process; +import android.util.Log; + +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. + */ +public abstract class SocketKeepalive implements AutoCloseable { + static final String TAG = "SocketKeepalive"; + + /** @hide */ + 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 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; + + /** @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 {} + + /** + * 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; + + @NonNull final IConnectivityManager mService; + @NonNull final Network mNetwork; + @NonNull private final Executor mExecutor; + @NonNull private final SocketKeepalive.Callback mCallback; + @NonNull private final Looper mLooper; + @NonNull final Messenger mMessenger; + @NonNull Integer mSlot; + + SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, + @NonNull Executor executor, @NonNull Callback callback) { + mService = service; + mNetwork = network; + mExecutor = executor; + mCallback = callback; + // TODO: 1. Use other thread modeling instead of create one thread for every instance to + // reduce the memory cost. + // 2. support restart. + // 3. Fix race condition which caused by rapidly start and stop. + HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND + + Process.THREAD_PRIORITY_LESS_FAVORABLE); + thread.start(); + mLooper = thread.getLooper(); + mMessenger = new Messenger(new Handler(mLooper) { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case NetworkAgent.EVENT_PACKET_KEEPALIVE: + final int status = message.arg2; + try { + if (status == SUCCESS) { + if (mSlot == null) { + mSlot = message.arg1; + mExecutor.execute(() -> mCallback.onStarted()); + } else { + mSlot = null; + stopLooper(); + mExecutor.execute(() -> mCallback.onStopped()); + } + } else if (status == DATA_RECEIVED) { + stopLooper(); + mExecutor.execute(() -> mCallback.onDataReceived()); + } else { + stopLooper(); + mExecutor.execute(() -> mCallback.onError(status)); + } + } catch (Exception e) { + Log.e(TAG, "Exception in keepalive callback(" + status + ")", e); + } + break; + default: + Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what)); + break; + } + } + }); + } + + /** + * Request that keepalive be started with the given {@code intervalSec}. See + * {@link SocketKeepalive}. + * + * @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); + + /** @hide */ + protected void stopLooper() { + // TODO: remove this after changing thread modeling. + mLooper.quit(); + } + + /** + * 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(); + stopLooper(); + } + + /** + * 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. */ + public void onDataReceived() {} + } +} diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java index 5397b57a4568..6a9eae00e3ff 100644 --- a/core/java/android/net/ipmemorystore/NetworkAttributes.java +++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java @@ -252,6 +252,12 @@ public class NetworkAttributes { } } + /** @hide */ + public boolean isEmpty() { + return (null == assignedV4Address) && (null == groupHint) + && (null == dnsAddresses) && (null == mtu); + } + @Override public boolean equals(@Nullable final Object o) { if (!(o instanceof NetworkAttributes)) return false; diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java index 30c5cd98b719..30c5cd98b719 100644 --- a/services/net/java/android/net/util/MultinetworkPolicyTracker.java +++ b/core/java/android/net/util/MultinetworkPolicyTracker.java diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index c5a51f133047..518528dd1a5b 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -79,23 +79,27 @@ public class BugreportManager { * Called when taking bugreport resulted in an error. * * @param errorCode the error that occurred. Possible values are - * {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}. + * {@code BUGREPORT_ERROR_INVALID_INPUT}, + * {@code BUGREPORT_ERROR_RUNTIME}, + * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}. */ void onError(@BugreportErrorCode int errorCode); /** - * Called when taking bugreport finishes successfully - * - * @param durationMs time capturing bugreport took in milliseconds - * @param title title for the bugreport; helpful in reminding the user why they took it - * @param description detailed description for the bugreport + * Called when taking bugreport finishes successfully. */ - void onFinished(long durationMs, @NonNull String title, - @NonNull String description); + void onFinished(); } /** - * Starts a bugreport asynchronously. + * Starts a bugreport. + * + * <p>This starts a bugreport in the background. However the call itself can take several + * seconds to return in the worst case. {@code listener} will receive progress and status + * updates. + * + * <p>The bugreport artifacts will be copied over to the given file descriptors only if the + * user consents to sharing with the calling app. * * @param bugreportFd file to write the bugreport. This should be opened in write-only, * append mode. @@ -107,7 +111,7 @@ public class BugreportManager { @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull FileDescriptor bugreportFd, @Nullable FileDescriptor screenshotFd, - @NonNull BugreportParams params, @Nullable BugreportListener listener) { + @NonNull BugreportParams params, @NonNull BugreportListener listener) { // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. DumpstateListener dsListener = new DumpstateListener(listener); @@ -121,6 +125,18 @@ public class BugreportManager { } } + /* + * Cancels a currently running bugreport. + */ + @RequiresPermission(android.Manifest.permission.DUMP) + public void cancelBugreport() { + try { + mBinder.cancelBugreport(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { private final BugreportListener mListener; @@ -145,9 +161,13 @@ public class BugreportManager { } @Override - public void onFinished(long durationMs, String title, String description) - throws RemoteException { - mListener.onFinished(durationMs, title, description); + public void onFinished() throws RemoteException { + try { + mListener.onFinished(); + } finally { + // The bugreport has finished. Let's shutdown the service to minimize its footprint. + cancelBugreport(); + } } // Old methods; should go away diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 8254c534c7df..c7afd411936c 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -30,16 +30,6 @@ public class Process { private static final String LOG_TAG = "Process"; /** - * @hide for internal use only. - */ - public static final String ZYGOTE_SOCKET = "zygote"; - - /** - * @hide for internal use only. - */ - public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary"; - - /** * An invalid UID value. */ public static final int INVALID_UID = -1; @@ -454,8 +444,7 @@ public class Process { * State associated with the zygote process. * @hide */ - public static final ZygoteProcess zygoteProcess = - new ZygoteProcess(ZYGOTE_SOCKET, SECONDARY_ZYGOTE_SOCKET); + public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess(); /** * Start a new process. @@ -507,7 +496,7 @@ public class Process { String appDataDir, String invokeWith, String[] zygoteArgs) { - return zygoteProcess.start(processClass, niceName, uid, gid, gids, + return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 021e72f7a082..f0bdaecca1ef 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -58,87 +58,119 @@ import java.util.UUID; * {@hide} */ public class ZygoteProcess { + + /** + * @hide for internal use only. + */ + public static final String ZYGOTE_SOCKET_NAME = "zygote"; + + /** + * @hide for internal use only. + */ + public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary"; + + /** + * @hide for internal use only + */ private static final String LOG_TAG = "ZygoteProcess"; /** * The name of the socket used to communicate with the primary zygote. */ - private final LocalSocketAddress mSocket; + private final LocalSocketAddress mZygoteSocketAddress; /** * The name of the secondary (alternate ABI) zygote socket. */ - private final LocalSocketAddress mSecondarySocket; - - public ZygoteProcess(String primarySocket, String secondarySocket) { - this(new LocalSocketAddress(primarySocket, LocalSocketAddress.Namespace.RESERVED), - new LocalSocketAddress(secondarySocket, LocalSocketAddress.Namespace.RESERVED)); + private final LocalSocketAddress mZygoteSecondarySocketAddress; + + public ZygoteProcess() { + mZygoteSocketAddress = + new LocalSocketAddress(ZYGOTE_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); + mZygoteSecondarySocketAddress = + new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME, + LocalSocketAddress.Namespace.RESERVED); } - public ZygoteProcess(LocalSocketAddress primarySocket, LocalSocketAddress secondarySocket) { - mSocket = primarySocket; - mSecondarySocket = secondarySocket; + public ZygoteProcess(LocalSocketAddress primarySocketAddress, + LocalSocketAddress secondarySocketAddress) { + mZygoteSocketAddress = primarySocketAddress; + mZygoteSecondarySocketAddress = secondarySocketAddress; } public LocalSocketAddress getPrimarySocketAddress() { - return mSocket; + return mZygoteSocketAddress; } /** * State for communicating with the zygote process. */ public static class ZygoteState { - final LocalSocket socket; - final DataInputStream inputStream; - final BufferedWriter writer; - final List<String> abiList; + final LocalSocketAddress mZygoteSocketAddress; + + private final LocalSocket mZygoteSessionSocket; + + final DataInputStream mZygoteInputStream; + final BufferedWriter mZygoteOutputWriter; - boolean mClosed; + private final List<String> mABIList; - private ZygoteState(LocalSocket socket, DataInputStream inputStream, - BufferedWriter writer, List<String> abiList) { - this.socket = socket; - this.inputStream = inputStream; - this.writer = writer; - this.abiList = abiList; + private boolean mClosed; + + private ZygoteState(LocalSocketAddress zygoteSocketAddress, + LocalSocket zygoteSessionSocket, + DataInputStream zygoteInputStream, + BufferedWriter zygoteOutputWriter, + List<String> abiList) { + this.mZygoteSocketAddress = zygoteSocketAddress; + this.mZygoteSessionSocket = zygoteSessionSocket; + this.mZygoteInputStream = zygoteInputStream; + this.mZygoteOutputWriter = zygoteOutputWriter; + this.mABIList = abiList; } - public static ZygoteState connect(LocalSocketAddress address) throws IOException { + /** + * Create a new ZygoteState object by connecting to the given Zygote socket. + * + * @param zygoteSocketAddress Zygote socket to connect to + * @return A new ZygoteState object containing a session socket for the given Zygote socket + * address + * @throws IOException + */ + public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress) + throws IOException { + DataInputStream zygoteInputStream = null; - BufferedWriter zygoteWriter = null; - final LocalSocket zygoteSocket = new LocalSocket(); + BufferedWriter zygoteOutputWriter = null; + final LocalSocket zygoteSessionSocket = new LocalSocket(); try { - zygoteSocket.connect(address); - - zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream()); - - zygoteWriter = new BufferedWriter(new OutputStreamWriter( - zygoteSocket.getOutputStream()), 256); + zygoteSessionSocket.connect(zygoteSocketAddress); + zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream()); + zygoteOutputWriter = + new BufferedWriter( + new OutputStreamWriter(zygoteSessionSocket.getOutputStream()), + 256); } catch (IOException ex) { try { - zygoteSocket.close(); - } catch (IOException ignore) { - } + zygoteSessionSocket.close(); + } catch (IOException ignore) { } throw ex; } - String abiListString = getAbiList(zygoteWriter, zygoteInputStream); - Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/" - + address.getName() + " opened, supported ABIS: " + abiListString); - - return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, - Arrays.asList(abiListString.split(","))); + return new ZygoteState(zygoteSocketAddress, + zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter, + getAbiList(zygoteOutputWriter, zygoteInputStream)); } boolean matches(String abi) { - return abiList.contains(abi); + return mABIList.contains(abi); } public void close() { try { - socket.close(); + mZygoteSessionSocket.close(); } catch (IOException ex) { Log.e(LOG_TAG,"I/O exception on routine close", ex); } @@ -231,8 +263,8 @@ public class ZygoteProcess { try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */, - zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, + /*startChildZygote=*/false, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -250,7 +282,7 @@ public class ZygoteProcess { * @throws ZygoteStartFailedEx if the query failed. */ @GuardedBy("mLock") - private static String getAbiList(BufferedWriter writer, DataInputStream inputStream) + private static List<String> getAbiList(BufferedWriter writer, DataInputStream inputStream) throws IOException { // Each query starts with the argument count (1 in this case) writer.write("1"); @@ -266,7 +298,9 @@ public class ZygoteProcess { byte[] bytes = new byte[numBytes]; inputStream.readFully(bytes); - return new String(bytes, StandardCharsets.US_ASCII); + String rawList = new String(bytes, StandardCharsets.US_ASCII); + + return Arrays.asList(rawList.split(",")); } /** @@ -300,8 +334,8 @@ public class ZygoteProcess { * the child or -1 on failure, followed by boolean to * indicate whether a wrapper process was used. */ - final BufferedWriter writer = zygoteState.writer; - final DataInputStream inputStream = zygoteState.inputStream; + final BufferedWriter writer = zygoteState.mZygoteOutputWriter; + final DataInputStream inputStream = zygoteState.mZygoteInputStream; writer.write(Integer.toString(args.size())); writer.newLine(); @@ -475,18 +509,18 @@ public class ZygoteProcess { ZygoteState state = openZygoteSocketIfNeeded(abi); // Each query starts with the argument count (1 in this case) - state.writer.write("1"); + state.mZygoteOutputWriter.write("1"); // ... followed by a new-line. - state.writer.newLine(); + state.mZygoteOutputWriter.newLine(); // ... followed by our only argument. - state.writer.write("--get-pid"); - state.writer.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.write("--get-pid"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); // The response is a length prefixed stream of ASCII bytes. - int numBytes = state.inputStream.readInt(); + int numBytes = state.mZygoteInputStream.readInt(); byte[] bytes = new byte[numBytes]; - state.inputStream.readFully(bytes); + state.mZygoteInputStream.readFully(bytes); return Integer.parseInt(new String(bytes, StandardCharsets.US_ASCII)); } @@ -540,16 +574,16 @@ public class ZygoteProcess { return true; } try { - state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); - state.writer.newLine(); - state.writer.write("--set-api-blacklist-exemptions"); - state.writer.newLine(); + state.mZygoteOutputWriter.write(Integer.toString(mApiBlacklistExemptions.size() + 1)); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--set-api-blacklist-exemptions"); + state.mZygoteOutputWriter.newLine(); for (int i = 0; i < mApiBlacklistExemptions.size(); ++i) { - state.writer.write(mApiBlacklistExemptions.get(i)); - state.writer.newLine(); + state.mZygoteOutputWriter.write(mApiBlacklistExemptions.get(i)); + state.mZygoteOutputWriter.newLine(); } - state.writer.flush(); - int status = state.inputStream.readInt(); + state.mZygoteOutputWriter.flush(); + int status = state.mZygoteInputStream.readInt(); if (status != 0) { Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status); } @@ -569,13 +603,13 @@ public class ZygoteProcess { return; } try { - state.writer.write(Integer.toString(1)); - state.writer.newLine(); - state.writer.write("--hidden-api-log-sampling-rate=" + state.mZygoteOutputWriter.write(Integer.toString(1)); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--hidden-api-log-sampling-rate=" + Integer.toString(mHiddenApiAccessLogSampleRate)); - state.writer.newLine(); - state.writer.flush(); - int status = state.inputStream.readInt(); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); + int status = state.mZygoteInputStream.readInt(); if (status != 0) { Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status); } @@ -585,22 +619,29 @@ public class ZygoteProcess { } /** - * Tries to open socket to Zygote process if not already open. If - * already open, does nothing. May block and retry. Requires that mLock be held. + * Tries to open a session socket to a Zygote process with a compatible ABI if one is not + * already open. If a compatible session socket is already open that session socket is returned. + * This function may block and may have to try connecting to multiple Zygotes to find the + * appropriate one. Requires that mLock be held. */ @GuardedBy("mLock") - private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { + private ZygoteState openZygoteSocketIfNeeded(String abi) + throws ZygoteStartFailedEx { + Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); if (primaryZygoteState == null || primaryZygoteState.isClosed()) { try { - primaryZygoteState = ZygoteState.connect(mSocket); + primaryZygoteState = + ZygoteState.connect(mZygoteSocketAddress); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); } + maybeSetApiBlacklistExemptions(primaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState); } + if (primaryZygoteState.matches(abi)) { return primaryZygoteState; } @@ -608,10 +649,12 @@ public class ZygoteProcess { // The primary zygote didn't match. Try the secondary. if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { try { - secondaryZygoteState = ZygoteState.connect(mSecondarySocket); + secondaryZygoteState = + ZygoteState.connect(mZygoteSecondarySocketAddress); } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); } + maybeSetApiBlacklistExemptions(secondaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState); } @@ -632,27 +675,27 @@ public class ZygoteProcess { IOException { synchronized(mLock) { ZygoteState state = openZygoteSocketIfNeeded(abi); - state.writer.write("5"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("5"); + state.mZygoteOutputWriter.newLine(); - state.writer.write("--preload-package"); - state.writer.newLine(); + state.mZygoteOutputWriter.write("--preload-package"); + state.mZygoteOutputWriter.newLine(); - state.writer.write(packagePath); - state.writer.newLine(); + state.mZygoteOutputWriter.write(packagePath); + state.mZygoteOutputWriter.newLine(); - state.writer.write(libsPath); - state.writer.newLine(); + state.mZygoteOutputWriter.write(libsPath); + state.mZygoteOutputWriter.newLine(); - state.writer.write(libFileName); - state.writer.newLine(); + state.mZygoteOutputWriter.write(libFileName); + state.mZygoteOutputWriter.newLine(); - state.writer.write(cacheKey); - state.writer.newLine(); + state.mZygoteOutputWriter.write(cacheKey); + state.mZygoteOutputWriter.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.flush(); - return (state.inputStream.readInt() == 0); + return (state.mZygoteInputStream.readInt() == 0); } } @@ -666,13 +709,13 @@ public class ZygoteProcess { synchronized (mLock) { ZygoteState state = openZygoteSocketIfNeeded(abi); // Each query starts with the argument count (1 in this case) - state.writer.write("1"); - state.writer.newLine(); - state.writer.write("--preload-default"); - state.writer.newLine(); - state.writer.flush(); + state.mZygoteOutputWriter.write("1"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.write("--preload-default"); + state.mZygoteOutputWriter.newLine(); + state.mZygoteOutputWriter.flush(); - return (state.inputStream.readInt() == 0); + return (state.mZygoteInputStream.readInt() == 0); } } @@ -680,20 +723,21 @@ public class ZygoteProcess { * Try connecting to the Zygote over and over again until we hit a time-out. * @param socketName The name of the socket to connect to. */ - public static void waitForConnectionToZygote(String socketName) { - final LocalSocketAddress address = - new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.RESERVED); - waitForConnectionToZygote(address); + public static void waitForConnectionToZygote(String zygoteSocketName) { + final LocalSocketAddress zygoteSocketAddress = + new LocalSocketAddress(zygoteSocketName, LocalSocketAddress.Namespace.RESERVED); + waitForConnectionToZygote(zygoteSocketAddress); } /** * Try connecting to the Zygote over and over again until we hit a time-out. * @param address The name of the socket to connect to. */ - public static void waitForConnectionToZygote(LocalSocketAddress address) { + public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) { for (int n = 20; n >= 0; n--) { try { - final ZygoteState zs = ZygoteState.connect(address); + final ZygoteState zs = + ZygoteState.connect(zygoteSocketAddress); zs.close(); return; } catch (IOException ioe) { @@ -706,7 +750,8 @@ public class ZygoteProcess { } catch (InterruptedException ie) { } } - Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + address.getName()); + Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + + zygoteSocketAddress.getName()); } /** diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 49e11b8baf51..383530de6d84 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -150,7 +150,7 @@ public class WebViewZygote { } try { - sZygote = Process.zygoteProcess.startChildZygote( + sZygote = Process.ZYGOTE_PROCESS.startChildZygote( "com.android.internal.os.WebViewZygoteInit", "webview_zygote", Process.WEBVIEW_ZYGOTE_UID, diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index 9f2434e97d7a..955fef0542fd 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -18,7 +18,6 @@ package com.android.internal.os; import android.app.ApplicationLoaders; import android.net.LocalSocket; -import android.net.LocalServerSocket; import android.os.Build; import android.system.ErrnoException; import android.system.Os; diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 8b3829b3d6b7..382542a1b458 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -16,13 +16,23 @@ package com.android.internal.os; +import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; + +import android.net.Credentials; +import android.os.FactoryTest; import android.os.IVold; +import android.os.Process; +import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; +import android.util.Log; import dalvik.system.ZygoteHooks; +import java.io.BufferedReader; +import java.io.IOException; + /** @hide */ public final class Zygote { /* @@ -91,10 +101,13 @@ public final class Zygote { */ public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket="; + /** a prototype instance for a future List.toArray() */ + protected static final int[][] INT_ARRAY_2D = new int[0][0]; + private Zygote() {} /** Called for some security initialization before any fork. */ - native static void nativeSecurityInit(); + static native void nativeSecurityInit(); /** * Forks a new VM instance. The current VM must have been started @@ -131,14 +144,14 @@ public final class Zygote { * if this is the parent, or -1 on error. */ public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, - int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { + int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( - uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - fdsToIgnore, startChildZygote, instructionSet, appDataDir); + uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, + fdsToIgnore, startChildZygote, instructionSet, appDataDir); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -150,14 +163,19 @@ public final class Zygote { return pid; } - native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags, - int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir); + private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, + String appDataDir); + + private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + boolean startChildZygote, String instructionSet, String appDataDir); /** * Called to do any initialization before starting an application. */ - native static void nativePreApplicationInit(); + static native void nativePreApplicationInit(); /** * Special method to start the system server process. In addition to the @@ -188,7 +206,8 @@ public final class Zygote { // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkSystemServer( - uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities); + uid, gid, gids, runtimeFlags, rlimits, + permittedCapabilities, effectiveCapabilities); // Enable tracing as soon as we enter the system_server. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -197,19 +216,175 @@ public final class Zygote { return pid; } - native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, + private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); /** * Lets children of the zygote inherit open file descriptors to this path. */ - native protected static void nativeAllowFileAcrossFork(String path); + protected static native void nativeAllowFileAcrossFork(String path); /** * Zygote unmount storage space on initializing. * This method is called once. */ - native protected static void nativeUnmountStorageOnInit(); + protected static native void nativeUnmountStorageOnInit(); + + protected static native void nativeGetSocketFDs(boolean isPrimary); + + private static native int nativeGetBlastulaPoolCount(); + + private static native int nativeGetBlastulaPoolEventFD(); + + private static native int nativeForkBlastula(int readPipeFD, + int writePipeFD, + int[] sessionSocketRawFDs); + + private static native int[] nativeGetBlastulaPipeFDs(); + + private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID); + + /** + * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal + * operation. It may also specify any gid and setgroups() list it chooses. + * In factory test mode, it may specify any UID. + * + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException + */ + protected static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + + if (peer.getUid() == Process.SYSTEM_UID) { + /* In normal operation, SYSTEM_UID can only specify a restricted + * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. + */ + boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; + + if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) { + throw new ZygoteSecurityException( + "System UID may not launch process with UID < " + + Process.SYSTEM_UID); + } + } + + // If not otherwise specified, uid and gid are inherited from peer + if (!args.mUidSpecified) { + args.mUid = peer.getUid(); + args.mUidSpecified = true; + } + if (!args.mGidSpecified) { + args.mGid = peer.getGid(); + args.mGidSpecified = true; + } + } + + /** + * Applies debugger system properties to the zygote arguments. + * + * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, + * the debugger state is specified via the "--enable-jdwp" flag + * in the spawn request. + * + * @param args non-null; zygote spawner args + */ + protected static void applyDebuggerSystemProperty(ZygoteArguments args) { + if (RoSystemProperties.DEBUGGABLE) { + args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; + } + } + + /** + * Applies zygote security policy. + * Based on the credentials of the process issuing a zygote command: + * <ol> + * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a + * wrapper command. + * <li> Any other uid may not specify any invoke-with argument. + * </ul> + * + * @param args non-null; zygote spawner arguments + * @param peer non-null; peer credentials + * @throws ZygoteSecurityException + */ + protected static void applyInvokeWithSecurityPolicy(ZygoteArguments args, Credentials peer) + throws ZygoteSecurityException { + int peerUid = peer.getUid(); + + if (args.mInvokeWith != null && peerUid != 0 + && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { + throw new ZygoteSecurityException("Peer is permitted to specify an" + + "explicit invoke-with wrapper command only for debuggable" + + "applications."); + } + } + + /** + * Applies invoke-with system properties to the zygote arguments. + * + * @param args non-null; zygote args + */ + protected static void applyInvokeWithSystemProperty(ZygoteArguments args) { + if (args.mInvokeWith == null && args.mNiceName != null) { + String property = "wrap." + args.mNiceName; + args.mInvokeWith = SystemProperties.get(property); + if (args.mInvokeWith != null && args.mInvokeWith.length() == 0) { + args.mInvokeWith = null; + } + } + } + + /** + * Reads an argument list from the provided socket + * @return Argument list or null if EOF is reached + * @throws IOException passed straight through + */ + static String[] readArgumentList(BufferedReader socketReader) throws IOException { + + /** + * See android.os.Process.zygoteSendArgsAndGetPid() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure. + */ + + int argc; + + try { + String argc_string = socketReader.readLine(); + + if (argc_string == null) { + // EOF reached. + return null; + } + argc = Integer.parseInt(argc_string); + + } catch (NumberFormatException ex) { + Log.e("Zygote", "Invalid Zygote wire format: non-int at argc"); + throw new IOException("Invalid wire format"); + } + + // See bug 1092107: large argc can be used for a DOS attack + if (argc > MAX_ZYGOTE_ARGC) { + throw new IOException("Max arg count exceeded"); + } + + String[] args = new String[argc]; + for (int arg_index = 0; arg_index < argc; arg_index++) { + args[arg_index] = socketReader.readLine(); + if (args[arg_index] == null) { + // We got an unexpected EOF. + throw new IOException("Truncated request"); + } + } + + return args; + } + private static void callPostForkSystemServerHooks() { // SystemServer specific post fork hooks run before child post fork hooks. diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java new file mode 100644 index 000000000000..2e869aefcb77 --- /dev/null +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -0,0 +1,390 @@ +/* + * 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 com.android.internal.os; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Handles argument parsing for args related to the zygote spawner. + * + * Current recognized args: + * <ul> + * <li> --setuid=<i>uid of child process, defaults to 0</i> + * <li> --setgid=<i>gid of child process, defaults to 0</i> + * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i> + * <li> --capabilities=<i>a pair of comma-separated integer strings + * indicating Linux capabilities(2) set for child. The first string represents the + * <code>permitted</code> set, and the second the + * <code>effective</code> set. Precede each with 0 or + * 0x for octal or hexidecimal value. If unspecified, both default to 0. This parameter is only + * applied if the uid of the new process will be non-0. </i> + * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call. + * <code>r</code> is the resource, <code>c</code> and <code>m</code> + * are the settings for current and max value.</i> + * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate. + * <li> --nice-name=<i>nice name to appear in ps</i> + * <li> --runtime-args indicates that the remaining arg list should + * be handed off to com.android.internal.os.RuntimeInit, rather than processed directly. Android + * runtime startup (eg, Binder initialization) is also eschewed. + * <li> [--] <args for RuntimeInit > + * </ul> + */ +class ZygoteArguments { + + /** + * from --setuid + */ + int mUid = 0; + boolean mUidSpecified; + + /** + * from --setgid + */ + int mGid = 0; + boolean mGidSpecified; + + /** + * from --setgroups + */ + int[] mGids; + + /** + * From --runtime-flags. + */ + int mRuntimeFlags; + + /** + * From --mount-external + */ + int mMountExternal = Zygote.MOUNT_EXTERNAL_NONE; + + /** + * from --target-sdk-version. + */ + int mTargetSdkVersion; + boolean mTargetSdkVersionSpecified; + + /** + * from --nice-name + */ + String mNiceName; + + /** + * from --capabilities + */ + boolean mCapabilitiesSpecified; + long mPermittedCapabilities; + long mEffectiveCapabilities; + + /** + * from --seinfo + */ + boolean mSeInfoSpecified; + String mSeInfo; + + /** + * from all --rlimit=r,c,m + */ + ArrayList<int[]> mRLimits; + + /** + * from --invoke-with + */ + String mInvokeWith; + + /** + * Any args after and including the first non-option arg (or after a '--') + */ + String[] mRemainingArgs; + + /** + * Whether the current arguments constitute an ABI list query. + */ + boolean mAbiListQuery; + + /** + * The instruction set to use, or null when not important. + */ + String mInstructionSet; + + /** + * The app data directory. May be null, e.g., for the system server. Note that this might not be + * reliable in the case of process-sharing apps. + */ + String mAppDataDir; + + /** + * The APK path of the package to preload, when using --preload-package. + */ + String mPreloadPackage; + + /** + * The native library path of the package to preload, when using --preload-package. + */ + String mPreloadPackageLibs; + + /** + * The filename of the native library to preload, when using --preload-package. + */ + String mPreloadPackageLibFileName; + + /** + * The cache key under which to enter the preloaded package into the classloader cache, when + * using --preload-package. + */ + String mPreloadPackageCacheKey; + + /** + * Whether this is a request to start preloading the default resources and classes. This + * argument only makes sense when the zygote is in lazy preload mode (i.e, when it's started + * with --enable-lazy-preload). + */ + boolean mPreloadDefault; + + /** + * Whether this is a request to start a zygote process as a child of this zygote. Set with + * --start-child-zygote. The remaining arguments must include the CHILD_ZYGOTE_SOCKET_NAME_ARG + * flag to indicate the abstract socket name that should be used for communication. + */ + boolean mStartChildZygote; + + /** + * Whether the current arguments constitute a request for the zygote's PID. + */ + boolean mPidQuery; + + /** + * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, or + * when they change, via --set-api-blacklist-exemptions. + */ + String[] mApiBlacklistExemptions; + + /** + * Sampling rate for logging hidden API accesses to the event log. This is sent to the + * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate. + */ + int mHiddenApiAccessLogSampleRate = -1; + + /** + * Constructs instance and parses args + * + * @param args zygote command-line args + */ + ZygoteArguments(String[] args) throws IllegalArgumentException { + parseArgs(args); + } + + /** + * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and + * "--setgid=") and creates an array containing the remaining args. + * + * Per security review bug #1112214, duplicate args are disallowed in critical cases to make + * injection harder. + */ + private void parseArgs(String[] args) + throws IllegalArgumentException { + int curArg = 0; + + boolean seenRuntimeArgs = false; + + boolean expectRuntimeArgs = true; + for ( /* curArg */ ; curArg < args.length; curArg++) { + String arg = args[curArg]; + + if (arg.equals("--")) { + curArg++; + break; + } else if (arg.startsWith("--setuid=")) { + if (mUidSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mUidSpecified = true; + mUid = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--setgid=")) { + if (mGidSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mGidSpecified = true; + mGid = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--target-sdk-version=")) { + if (mTargetSdkVersionSpecified) { + throw new IllegalArgumentException( + "Duplicate target-sdk-version specified"); + } + mTargetSdkVersionSpecified = true; + mTargetSdkVersion = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.equals("--runtime-args")) { + seenRuntimeArgs = true; + } else if (arg.startsWith("--runtime-flags=")) { + mRuntimeFlags = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); + } else if (arg.startsWith("--seinfo=")) { + if (mSeInfoSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mSeInfoSpecified = true; + mSeInfo = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--capabilities=")) { + if (mCapabilitiesSpecified) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mCapabilitiesSpecified = true; + String capString = arg.substring(arg.indexOf('=') + 1); + + String[] capStrings = capString.split(",", 2); + + if (capStrings.length == 1) { + mEffectiveCapabilities = Long.decode(capStrings[0]); + mPermittedCapabilities = mEffectiveCapabilities; + } else { + mPermittedCapabilities = Long.decode(capStrings[0]); + mEffectiveCapabilities = Long.decode(capStrings[1]); + } + } else if (arg.startsWith("--rlimit=")) { + // Duplicate --rlimit arguments are specifically allowed. + String[] limitStrings = arg.substring(arg.indexOf('=') + 1).split(","); + + if (limitStrings.length != 3) { + throw new IllegalArgumentException( + "--rlimit= should have 3 comma-delimited ints"); + } + int[] rlimitTuple = new int[limitStrings.length]; + + for (int i = 0; i < limitStrings.length; i++) { + rlimitTuple[i] = Integer.parseInt(limitStrings[i]); + } + + if (mRLimits == null) { + mRLimits = new ArrayList(); + } + + mRLimits.add(rlimitTuple); + } else if (arg.startsWith("--setgroups=")) { + if (mGids != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + + String[] params = arg.substring(arg.indexOf('=') + 1).split(","); + + mGids = new int[params.length]; + + for (int i = params.length - 1; i >= 0; i--) { + mGids[i] = Integer.parseInt(params[i]); + } + } else if (arg.equals("--invoke-with")) { + if (mInvokeWith != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + try { + mInvokeWith = args[++curArg]; + } catch (IndexOutOfBoundsException ex) { + throw new IllegalArgumentException( + "--invoke-with requires argument"); + } + } else if (arg.startsWith("--nice-name=")) { + if (mNiceName != null) { + throw new IllegalArgumentException( + "Duplicate arg specified"); + } + mNiceName = arg.substring(arg.indexOf('=') + 1); + } else if (arg.equals("--mount-external-default")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; + } else if (arg.equals("--mount-external-read")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_READ; + } else if (arg.equals("--mount-external-write")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE; + } else if (arg.equals("--query-abi-list")) { + mAbiListQuery = true; + } else if (arg.equals("--get-pid")) { + mPidQuery = true; + } else if (arg.startsWith("--instruction-set=")) { + mInstructionSet = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--app-data-dir=")) { + mAppDataDir = arg.substring(arg.indexOf('=') + 1); + } else if (arg.equals("--preload-package")) { + mPreloadPackage = args[++curArg]; + mPreloadPackageLibs = args[++curArg]; + mPreloadPackageLibFileName = args[++curArg]; + mPreloadPackageCacheKey = args[++curArg]; + } else if (arg.equals("--preload-default")) { + mPreloadDefault = true; + expectRuntimeArgs = false; + } else if (arg.equals("--start-child-zygote")) { + mStartChildZygote = true; + } else if (arg.equals("--set-api-blacklist-exemptions")) { + // consume all remaining args; this is a stand-alone command, never included + // with the regular fork command. + mApiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); + curArg = args.length; + expectRuntimeArgs = false; + } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { + String rateStr = arg.substring(arg.indexOf('=') + 1); + try { + mHiddenApiAccessLogSampleRate = Integer.parseInt(rateStr); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid log sampling rate: " + rateStr, nfe); + } + expectRuntimeArgs = false; + } else { + break; + } + } + + if (mAbiListQuery || mPidQuery) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); + } + } else if (mPreloadPackage != null) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException( + "Unexpected arguments after --preload-package."); + } + } else if (expectRuntimeArgs) { + if (!seenRuntimeArgs) { + throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); + } + + mRemainingArgs = new String[args.length - curArg]; + System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length); + } + + if (mStartChildZygote) { + boolean seenChildSocketArg = false; + for (String arg : mRemainingArgs) { + if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { + seenChildSocketArg = true; + break; + } + } + if (!seenChildSocketArg) { + throw new IllegalArgumentException("--start-child-zygote specified " + + "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); + } + } + } +} diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 12761b9274e3..43f114f010bc 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -22,34 +22,31 @@ import static android.system.OsConstants.POLLIN; import static android.system.OsConstants.STDERR_FILENO; import static android.system.OsConstants.STDIN_FILENO; import static android.system.OsConstants.STDOUT_FILENO; + import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS; -import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS; import android.net.Credentials; import android.net.LocalSocket; -import android.os.FactoryTest; import android.os.Process; -import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; import android.system.StructPollfd; import android.util.Log; + import dalvik.system.VMRuntime; + +import libcore.io.IoUtils; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; -import java.io.EOFException; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; - -import libcore.io.IoUtils; /** * A connection that can make spawn requests. @@ -57,9 +54,6 @@ import libcore.io.IoUtils; class ZygoteConnection { private static final String TAG = "Zygote"; - /** a prototype instance for a future List.toArray() */ - private static final int[][] intArray2d = new int[0][0]; - /** * The command socket. * @@ -108,7 +102,7 @@ class ZygoteConnection { * * @return null-ok; file descriptor */ - FileDescriptor getFileDesciptor() { + FileDescriptor getFileDescriptor() { return mSocket.getFileDescriptor(); } @@ -122,11 +116,13 @@ class ZygoteConnection { */ Runnable processOneCommand(ZygoteServer zygoteServer) { String args[]; - Arguments parsedArgs = null; + ZygoteArguments parsedArgs = null; FileDescriptor[] descriptors; try { - args = readArgumentList(); + args = Zygote.readArgumentList(mSocketReader); + + // TODO (chriswailes): Remove this and add an assert. descriptors = mSocket.getAncillaryFileDescriptors(); } catch (IOException ex) { throw new IllegalStateException("IOException on command socket", ex); @@ -143,60 +139,60 @@ class ZygoteConnection { FileDescriptor childPipeFd = null; FileDescriptor serverPipeFd = null; - parsedArgs = new Arguments(args); + parsedArgs = new ZygoteArguments(args); - if (parsedArgs.abiListQuery) { + if (parsedArgs.mAbiListQuery) { handleAbiListQuery(); return null; } - if (parsedArgs.pidQuery) { + if (parsedArgs.mPidQuery) { handlePidQuery(); return null; } - if (parsedArgs.preloadDefault) { + if (parsedArgs.mPreloadDefault) { handlePreload(); return null; } - if (parsedArgs.preloadPackage != null) { - handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs, - parsedArgs.preloadPackageLibFileName, parsedArgs.preloadPackageCacheKey); + if (parsedArgs.mPreloadPackage != null) { + handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs, + parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey); return null; } - if (parsedArgs.apiBlacklistExemptions != null) { - handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions); + if (parsedArgs.mApiBlacklistExemptions != null) { + handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions); return null; } - if (parsedArgs.hiddenApiAccessLogSampleRate != -1) { - handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate); + if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) { + handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate); return null; } - if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) { - throw new ZygoteSecurityException("Client may not specify capabilities: " + - "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) + - ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities)); + if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) + + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities)); } - applyUidSecurityPolicy(parsedArgs, peer); - applyInvokeWithSecurityPolicy(parsedArgs, peer); + Zygote.applyUidSecurityPolicy(parsedArgs, peer); + Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); - applyDebuggerSystemProperty(parsedArgs); - applyInvokeWithSystemProperty(parsedArgs); + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); int[][] rlimits = null; - if (parsedArgs.rlimits != null) { - rlimits = parsedArgs.rlimits.toArray(intArray2d); + if (parsedArgs.mRLimits != null) { + rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); } int[] fdsToIgnore = null; - if (parsedArgs.invokeWith != null) { + if (parsedArgs.mInvokeWith != null) { try { FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); childPipeFd = pipeFds[1]; @@ -236,10 +232,10 @@ class ZygoteConnection { fd = null; - pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, - parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, - parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote, - parsedArgs.instructionSet, parsedArgs.appDataDir); + pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, + parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, + parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, + parsedArgs.mInstructionSet, parsedArgs.mAppDataDir); try { if (pid == 0) { @@ -251,7 +247,7 @@ class ZygoteConnection { serverPipeFd = null; return handleChildProc(parsedArgs, descriptors, childPipeFd, - parsedArgs.startChildZygote); + parsedArgs.mStartChildZygote); } else { // In the parent. A pid < 0 indicates a failure and will be handled in // handleParentProc. @@ -358,503 +354,6 @@ class ZygoteConnection { } /** - * Handles argument parsing for args related to the zygote spawner. - * - * Current recognized args: - * <ul> - * <li> --setuid=<i>uid of child process, defaults to 0</i> - * <li> --setgid=<i>gid of child process, defaults to 0</i> - * <li> --setgroups=<i>comma-separated list of supplimentary gid's</i> - * <li> --capabilities=<i>a pair of comma-separated integer strings - * indicating Linux capabilities(2) set for child. The first string - * represents the <code>permitted</code> set, and the second the - * <code>effective</code> set. Precede each with 0 or - * 0x for octal or hexidecimal value. If unspecified, both default to 0. - * This parameter is only applied if the uid of the new process will - * be non-0. </i> - * <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call. - * <code>r</code> is the resource, <code>c</code> and <code>m</code> - * are the settings for current and max value.</i> - * <li> --instruction-set=<i>instruction-set-string</i> which instruction set to use/emulate. - * <li> --nice-name=<i>nice name to appear in ps</i> - * <li> --runtime-args indicates that the remaining arg list should - * be handed off to com.android.internal.os.RuntimeInit, rather than - * processed directly. - * Android runtime startup (eg, Binder initialization) is also eschewed. - * <li> [--] <args for RuntimeInit > - * </ul> - */ - static class Arguments { - /** from --setuid */ - int uid = 0; - boolean uidSpecified; - - /** from --setgid */ - int gid = 0; - boolean gidSpecified; - - /** from --setgroups */ - int[] gids; - - /** - * From --runtime-flags. - */ - int runtimeFlags; - - /** From --mount-external */ - int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; - - /** from --target-sdk-version. */ - int targetSdkVersion; - boolean targetSdkVersionSpecified; - - /** from --nice-name */ - String niceName; - - /** from --capabilities */ - boolean capabilitiesSpecified; - long permittedCapabilities; - long effectiveCapabilities; - - /** from --seinfo */ - boolean seInfoSpecified; - String seInfo; - - /** from all --rlimit=r,c,m */ - ArrayList<int[]> rlimits; - - /** from --invoke-with */ - String invokeWith; - - /** - * Any args after and including the first non-option arg - * (or after a '--') - */ - String remainingArgs[]; - - /** - * Whether the current arguments constitute an ABI list query. - */ - boolean abiListQuery; - - /** - * The instruction set to use, or null when not important. - */ - String instructionSet; - - /** - * The app data directory. May be null, e.g., for the system server. Note that this might - * not be reliable in the case of process-sharing apps. - */ - String appDataDir; - - /** - * The APK path of the package to preload, when using --preload-package. - */ - String preloadPackage; - - /** - * The native library path of the package to preload, when using --preload-package. - */ - String preloadPackageLibs; - - /** - * The filename of the native library to preload, when using --preload-package. - */ - String preloadPackageLibFileName; - - /** - * The cache key under which to enter the preloaded package into the classloader cache, - * when using --preload-package. - */ - String preloadPackageCacheKey; - - /** - * Whether this is a request to start preloading the default resources and classes. - * This argument only makes sense when the zygote is in lazy preload mode (i.e, when - * it's started with --enable-lazy-preload). - */ - boolean preloadDefault; - - /** - * Whether this is a request to start a zygote process as a child of this zygote. - * Set with --start-child-zygote. The remaining arguments must include the - * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that - * should be used for communication. - */ - boolean startChildZygote; - - /** - * Whether the current arguments constitute a request for the zygote's PID. - */ - boolean pidQuery; - - /** - * Exemptions from API blacklisting. These are sent to the pre-forked zygote at boot time, - * or when they change, via --set-api-blacklist-exemptions. - */ - String[] apiBlacklistExemptions; - - /** - * Sampling rate for logging hidden API accesses to the event log. This is sent to the - * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate. - */ - int hiddenApiAccessLogSampleRate = -1; - - /** - * Constructs instance and parses args - * @param args zygote command-line args - * @throws IllegalArgumentException - */ - Arguments(String args[]) throws IllegalArgumentException { - parseArgs(args); - } - - /** - * Parses the commandline arguments intended for the Zygote spawner - * (such as "--setuid=" and "--setgid=") and creates an array - * containing the remaining args. - * - * Per security review bug #1112214, duplicate args are disallowed in - * critical cases to make injection harder. - */ - private void parseArgs(String args[]) - throws IllegalArgumentException { - int curArg = 0; - - boolean seenRuntimeArgs = false; - - boolean expectRuntimeArgs = true; - for ( /* curArg */ ; curArg < args.length; curArg++) { - String arg = args[curArg]; - - if (arg.equals("--")) { - curArg++; - break; - } else if (arg.startsWith("--setuid=")) { - if (uidSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - uidSpecified = true; - uid = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--setgid=")) { - if (gidSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - gidSpecified = true; - gid = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--target-sdk-version=")) { - if (targetSdkVersionSpecified) { - throw new IllegalArgumentException( - "Duplicate target-sdk-version specified"); - } - targetSdkVersionSpecified = true; - targetSdkVersion = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.equals("--runtime-args")) { - seenRuntimeArgs = true; - } else if (arg.startsWith("--runtime-flags=")) { - runtimeFlags = Integer.parseInt( - arg.substring(arg.indexOf('=') + 1)); - } else if (arg.startsWith("--seinfo=")) { - if (seInfoSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - seInfoSpecified = true; - seInfo = arg.substring(arg.indexOf('=') + 1); - } else if (arg.startsWith("--capabilities=")) { - if (capabilitiesSpecified) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - capabilitiesSpecified = true; - String capString = arg.substring(arg.indexOf('=')+1); - - String[] capStrings = capString.split(",", 2); - - if (capStrings.length == 1) { - effectiveCapabilities = Long.decode(capStrings[0]); - permittedCapabilities = effectiveCapabilities; - } else { - permittedCapabilities = Long.decode(capStrings[0]); - effectiveCapabilities = Long.decode(capStrings[1]); - } - } else if (arg.startsWith("--rlimit=")) { - // Duplicate --rlimit arguments are specifically allowed. - String[] limitStrings - = arg.substring(arg.indexOf('=')+1).split(","); - - if (limitStrings.length != 3) { - throw new IllegalArgumentException( - "--rlimit= should have 3 comma-delimited ints"); - } - int[] rlimitTuple = new int[limitStrings.length]; - - for(int i=0; i < limitStrings.length; i++) { - rlimitTuple[i] = Integer.parseInt(limitStrings[i]); - } - - if (rlimits == null) { - rlimits = new ArrayList(); - } - - rlimits.add(rlimitTuple); - } else if (arg.startsWith("--setgroups=")) { - if (gids != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - - String[] params - = arg.substring(arg.indexOf('=') + 1).split(","); - - gids = new int[params.length]; - - for (int i = params.length - 1; i >= 0 ; i--) { - gids[i] = Integer.parseInt(params[i]); - } - } else if (arg.equals("--invoke-with")) { - if (invokeWith != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - try { - invokeWith = args[++curArg]; - } catch (IndexOutOfBoundsException ex) { - throw new IllegalArgumentException( - "--invoke-with requires argument"); - } - } else if (arg.startsWith("--nice-name=")) { - if (niceName != null) { - throw new IllegalArgumentException( - "Duplicate arg specified"); - } - niceName = arg.substring(arg.indexOf('=') + 1); - } else if (arg.equals("--mount-external-default")) { - mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; - } else if (arg.equals("--mount-external-read")) { - mountExternal = Zygote.MOUNT_EXTERNAL_READ; - } else if (arg.equals("--mount-external-write")) { - mountExternal = Zygote.MOUNT_EXTERNAL_WRITE; - } else if (arg.equals("--query-abi-list")) { - abiListQuery = true; - } else if (arg.equals("--get-pid")) { - pidQuery = true; - } else if (arg.startsWith("--instruction-set=")) { - instructionSet = arg.substring(arg.indexOf('=') + 1); - } else if (arg.startsWith("--app-data-dir=")) { - appDataDir = arg.substring(arg.indexOf('=') + 1); - } else if (arg.equals("--preload-package")) { - preloadPackage = args[++curArg]; - preloadPackageLibs = args[++curArg]; - preloadPackageLibFileName = args[++curArg]; - preloadPackageCacheKey = args[++curArg]; - } else if (arg.equals("--preload-default")) { - preloadDefault = true; - expectRuntimeArgs = false; - } else if (arg.equals("--start-child-zygote")) { - startChildZygote = true; - } else if (arg.equals("--set-api-blacklist-exemptions")) { - // consume all remaining args; this is a stand-alone command, never included - // with the regular fork command. - apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); - curArg = args.length; - expectRuntimeArgs = false; - } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { - String rateStr = arg.substring(arg.indexOf('=') + 1); - try { - hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException( - "Invalid log sampling rate: " + rateStr, nfe); - } - expectRuntimeArgs = false; - } else { - break; - } - } - - if (abiListQuery || pidQuery) { - if (args.length - curArg > 0) { - throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); - } - } else if (preloadPackage != null) { - if (args.length - curArg > 0) { - throw new IllegalArgumentException( - "Unexpected arguments after --preload-package."); - } - } else if (expectRuntimeArgs) { - if (!seenRuntimeArgs) { - throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); - } - - remainingArgs = new String[args.length - curArg]; - System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length); - } - - if (startChildZygote) { - boolean seenChildSocketArg = false; - for (String arg : remainingArgs) { - if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { - seenChildSocketArg = true; - break; - } - } - if (!seenChildSocketArg) { - throw new IllegalArgumentException("--start-child-zygote specified " + - "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG); - } - } - } - } - - /** - * Reads an argument list from the command socket/ - * @return Argument list or null if EOF is reached - * @throws IOException passed straight through - */ - private String[] readArgumentList() - throws IOException { - - /** - * See android.os.Process.zygoteSendArgsAndGetPid() - * Presently the wire format to the zygote process is: - * a) a count of arguments (argc, in essence) - * b) a number of newline-separated argument strings equal to count - * - * After the zygote process reads these it will write the pid of - * the child or -1 on failure. - */ - - int argc; - - try { - String s = mSocketReader.readLine(); - - if (s == null) { - // EOF reached. - return null; - } - argc = Integer.parseInt(s); - } catch (NumberFormatException ex) { - Log.e(TAG, "invalid Zygote wire format: non-int at argc"); - throw new IOException("invalid wire format"); - } - - // See bug 1092107: large argc can be used for a DOS attack - if (argc > MAX_ZYGOTE_ARGC) { - throw new IOException("max arg count exceeded"); - } - - String[] result = new String[argc]; - for (int i = 0; i < argc; i++) { - result[i] = mSocketReader.readLine(); - if (result[i] == null) { - // We got an unexpected EOF. - throw new IOException("truncated request"); - } - } - - return result; - } - - /** - * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal - * operation. It may also specify any gid and setgroups() list it chooses. - * In factory test mode, it may specify any UID. - * - * @param args non-null; zygote spawner arguments - * @param peer non-null; peer credentials - * @throws ZygoteSecurityException - */ - private static void applyUidSecurityPolicy(Arguments args, Credentials peer) - throws ZygoteSecurityException { - - if (peer.getUid() == Process.SYSTEM_UID) { - /* In normal operation, SYSTEM_UID can only specify a restricted - * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. - */ - boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; - - if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) { - throw new ZygoteSecurityException( - "System UID may not launch process with UID < " - + Process.SYSTEM_UID); - } - } - - // If not otherwise specified, uid and gid are inherited from peer - if (!args.uidSpecified) { - args.uid = peer.getUid(); - args.uidSpecified = true; - } - if (!args.gidSpecified) { - args.gid = peer.getGid(); - args.gidSpecified = true; - } - } - - /** - * Applies debugger system properties to the zygote arguments. - * - * If "ro.debuggable" is "1", all apps are debuggable. Otherwise, - * the debugger state is specified via the "--enable-jdwp" flag - * in the spawn request. - * - * @param args non-null; zygote spawner args - */ - public static void applyDebuggerSystemProperty(Arguments args) { - if (RoSystemProperties.DEBUGGABLE) { - args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP; - } - } - - /** - * Applies zygote security policy. - * Based on the credentials of the process issuing a zygote command: - * <ol> - * <li> uid 0 (root) may specify --invoke-with to launch Zygote with a - * wrapper command. - * <li> Any other uid may not specify any invoke-with argument. - * </ul> - * - * @param args non-null; zygote spawner arguments - * @param peer non-null; peer credentials - * @throws ZygoteSecurityException - */ - private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer) - throws ZygoteSecurityException { - int peerUid = peer.getUid(); - - if (args.invokeWith != null && peerUid != 0 && - (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { - throw new ZygoteSecurityException("Peer is permitted to specify an" - + "explicit invoke-with wrapper command only for debuggable" - + "applications."); - } - } - - /** - * Applies invoke-with system properties to the zygote arguments. - * - * @param args non-null; zygote args - */ - public static void applyInvokeWithSystemProperty(Arguments args) { - if (args.invokeWith == null && args.niceName != null) { - String property = "wrap." + args.niceName; - args.invokeWith = SystemProperties.get(property); - if (args.invokeWith != null && args.invokeWith.length() == 0) { - args.invokeWith = null; - } - } - } - - /** * Handles post-fork setup of child proc, closing sockets as appropriate, * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller * if successful or returning if failed. @@ -864,7 +363,7 @@ class ZygoteConnection { * @param pipeFd null-ok; pipe for communication back to Zygote. * @param isZygote whether this new child process is itself a new Zygote. */ - private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors, + private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor[] descriptors, FileDescriptor pipeFd, boolean isZygote) { /** * By the time we get here, the native code has closed the two actual Zygote @@ -887,27 +386,27 @@ class ZygoteConnection { } } - if (parsedArgs.niceName != null) { - Process.setArgV0(parsedArgs.niceName); + if (parsedArgs.mNiceName != null) { + Process.setArgV0(parsedArgs.mNiceName); } // End of the postFork event. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - if (parsedArgs.invokeWith != null) { - WrapperInit.execApplication(parsedArgs.invokeWith, - parsedArgs.niceName, parsedArgs.targetSdkVersion, + if (parsedArgs.mInvokeWith != null) { + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), - pipeFd, parsedArgs.remainingArgs); + pipeFd, parsedArgs.mRemainingArgs); // Should not get here. throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); } else { if (!isZygote) { - return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, - null /* classLoader */); + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, null /* classLoader */); } else { - return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion, - parsedArgs.remainingArgs, null /* classLoader */); + return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, null /* classLoader */); } } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index c2c6ae6712ab..2f00c07247db 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -21,7 +21,6 @@ import static android.system.OsConstants.S_IRWXO; import android.content.res.Resources; import android.content.res.TypedArray; -import android.opengl.EGL14; import android.os.Build; import android.os.Environment; import android.os.IInstalld; @@ -71,16 +70,16 @@ import java.security.Security; /** * Startup class for the zygote process. * - * Pre-initializes some classes, and then waits for commands on a UNIX domain - * socket. Based on these commands, forks off child processes that inherit - * the initial state of the VM. + * Pre-initializes some classes, and then waits for commands on a UNIX domain socket. Based on these + * commands, forks off child processes that inherit the initial state of the VM. * - * Please see {@link ZygoteConnection.Arguments} for documentation on the - * client protocol. + * Please see {@link ZygoteArguments} for documentation on the client protocol. * * @hide */ public class ZygoteInit { + + // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate private static final String TAG = "Zygote"; private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload"; @@ -89,11 +88,15 @@ public class ZygoteInit { private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020; private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030; - /** when preloading, GC after allocating this many bytes */ + /** + * when preloading, GC after allocating this many bytes + */ private static final int PRELOAD_GC_THRESHOLD = 50000; private static final String ABI_LIST_ARG = "--abi-list="; + // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a + // --blastula-socket-name parameter. private static final String SOCKET_NAME_ARG = "--socket-name="; /** @@ -106,7 +109,9 @@ public class ZygoteInit { */ private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes"; - /** Controls whether we should preload resources during zygote init. */ + /** + * Controls whether we should preload resources during zygote init. + */ public static final boolean PRELOAD_RESOURCES = true; private static final int UNPRIVILEGED_UID = 9999; @@ -173,6 +178,7 @@ public class ZygoteInit { } native private static void nativePreloadAppProcessHALs(); + native private static void nativePreloadOpenGL(); private static void preloadOpenGL() { @@ -191,8 +197,8 @@ public class ZygoteInit { /** * Register AndroidKeyStoreProvider and warm up the providers that are already registered. * - * By doing it here we avoid that each app does it when requesting a service from the - * provider for the first time. + * By doing it here we avoid that each app does it when requesting a service from the provider + * for the first time. */ private static void warmUpJcaProviders() { long startTime = SystemClock.uptimeMillis(); @@ -218,11 +224,10 @@ public class ZygoteInit { } /** - * Performs Zygote process initialization. Loads and initializes - * commonly used classes. + * Performs Zygote process initialization. Loads and initializes commonly used classes. * - * Most classes only cause a few hundred bytes to be allocated, but - * a few will allocate a dozen Kbytes (in one case, 500+K). + * Most classes only cause a few hundred bytes to be allocated, but a few will allocate a dozen + * Kbytes (in one case, 500+K). */ private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); @@ -263,8 +268,8 @@ public class ZygoteInit { runtime.setTargetHeapUtilization(0.8f); try { - BufferedReader br - = new BufferedReader(new InputStreamReader(is), 256); + BufferedReader br = + new BufferedReader(new InputStreamReader(is), 256); int count = 0; String line; @@ -305,7 +310,7 @@ public class ZygoteInit { } Log.i(TAG, "...preloaded " + count + " classes in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); } catch (IOException e) { Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); } finally { @@ -331,11 +336,10 @@ public class ZygoteInit { } /** - * Load in commonly used resources, so they can be shared across - * processes. + * Load in commonly used resources, so they can be shared across processes. * - * These tend to be a few Kbytes, but are frequently in the 20-40K - * range, and occasionally even larger. + * These tend to be a few Kbytes, but are frequently in the 20-40K range, and occasionally even + * larger. */ private static void preloadResources() { final VMRuntime runtime = VMRuntime.getRuntime(); @@ -352,7 +356,7 @@ public class ZygoteInit { int N = preloadDrawables(ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); startTime = SystemClock.uptimeMillis(); ar = mResources.obtainTypedArray( @@ -360,7 +364,7 @@ public class ZygoteInit { N = preloadColorStateLists(ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " - + (SystemClock.uptimeMillis()-startTime) + "ms."); + + (SystemClock.uptimeMillis() - startTime) + "ms."); if (mResources.getBoolean( com.android.internal.R.bool.config_freeformWindowManagement)) { @@ -381,7 +385,7 @@ public class ZygoteInit { private static int preloadColorStateLists(TypedArray ar) { int N = ar.length(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); @@ -390,8 +394,8 @@ public class ZygoteInit { if (mResources.getColorStateList(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded color resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); } } } @@ -401,7 +405,7 @@ public class ZygoteInit { private static int preloadDrawables(TypedArray ar) { int N = ar.length(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); @@ -410,8 +414,8 @@ public class ZygoteInit { if (mResources.getDrawable(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded drawable resource #0x" - + Integer.toHexString(id) - + " (" + ar.getString(i) + ")"); + + Integer.toHexString(id) + + " (" + ar.getString(i) + ")"); } } } @@ -419,9 +423,8 @@ public class ZygoteInit { } /** - * Runs several special GCs to try to clean up a few generations of - * softly- and final-reachable objects, along with any other garbage. - * This is only useful just before a fork(). + * Runs several special GCs to try to clean up a few generations of softly- and final-reachable + * objects, along with any other garbage. This is only useful just before a fork(). */ private static void gcAndFinalize() { ZygoteHooks.gcAndFinalize(); @@ -430,12 +433,12 @@ public class ZygoteInit { /** * Finish remaining work for the newly forked system server process. */ - private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) { + private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) { // set umask to 0077 so new files and directories will default to owner-only permissions. Os.umask(S_IRWXG | S_IRWXO); - if (parsedArgs.niceName != null) { - Process.setArgV0(parsedArgs.niceName); + if (parsedArgs.mNiceName != null) { + Process.setArgV0(parsedArgs.mNiceName); } final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); @@ -454,8 +457,8 @@ public class ZygoteInit { } } - if (parsedArgs.invokeWith != null) { - String[] args = parsedArgs.remainingArgs; + if (parsedArgs.mInvokeWith != null) { + String[] args = parsedArgs.mRemainingArgs; // If we have a non-null system server class path, we'll have to duplicate the // existing arguments and append the classpath to it. ART will handle the classpath // correctly when we exec a new process. @@ -467,15 +470,15 @@ public class ZygoteInit { args = amendedArgs; } - WrapperInit.execApplication(parsedArgs.invokeWith, - parsedArgs.niceName, parsedArgs.targetSdkVersion, + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, VMRuntime.getCurrentInstructionSet(), null, args); throw new IllegalStateException("Unexpected return from WrapperInit.execApplication"); } else { ClassLoader cl = null; if (systemServerClasspath != null) { - cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion); + cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion); Thread.currentThread().setContextClassLoader(cl); } @@ -483,16 +486,17 @@ public class ZygoteInit { /* * Pass the remaining arguments to SystemServer. */ - return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mRemainingArgs, cl); } /* should never reach here */ } /** - * Note that preparing the profiles for system server does not require special - * selinux permissions. From the installer perspective the system server is a regular package - * which can capture profile information. + * Note that preparing the profiles for system server does not require special selinux + * permissions. From the installer perspective the system server is a regular package which can + * capture profile information. */ private static void prepareSystemServerProfile(String systemServerClasspath) throws RemoteException { @@ -544,8 +548,8 @@ public class ZygoteInit { } /** - * Performs dex-opt on the elements of {@code classPath}, if needed. We - * choose the instruction set of the current runtime. + * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction + * set of the current runtime. */ private static void performSystemServerDexOpt(String classPath) { final String[] classPathElements = classPath.split(":"); @@ -563,8 +567,9 @@ public class ZygoteInit { int dexoptNeeded; try { dexoptNeeded = DexFile.getDexOptNeeded( - classPathElement, instructionSet, systemServerFilter, - null /* classLoaderContext */, false /* newProfile */, false /* downgrade */); + classPathElement, instructionSet, systemServerFilter, + null /* classLoaderContext */, false /* newProfile */, + false /* downgrade */); } catch (FileNotFoundException ignored) { // Do not add to the classpath. Log.w(TAG, "Missing classpath element for system server: " + classPathElement); @@ -607,8 +612,8 @@ public class ZygoteInit { } /** - * Encodes the system server class loader context in a format that is accepted by dexopt. - * This assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}. + * Encodes the system server class loader context in a format that is accepted by dexopt. This + * assumes the system server is always loaded with a {@link dalvik.system.PathClassLoader}. * * Note that ideally we would use the {@code DexoptUtils} to compute this. However we have no * dependency here on the server so we hard code the logic again. @@ -619,10 +624,11 @@ public class ZygoteInit { /** * Encodes the class path in a format accepted by dexopt. - * @param classPath the old class path (may be empty). - * @param newElement the new class path elements - * @return the class path encoding resulted from appending {@code newElement} to - * {@code classPath}. + * + * @param classPath The old class path (may be empty). + * @param newElement The new class path elements + * @return The class path encoding resulted from appending {@code newElement} to {@code + * classPath}. */ private static String encodeSystemServerClassPath(String classPath, String newElement) { return (classPath == null || classPath.isEmpty()) @@ -633,25 +639,25 @@ public class ZygoteInit { /** * Prepare the arguments and forks for the system server process. * - * Returns an {@code Runnable} that provides an entrypoint into system_server code in the - * child process, and {@code null} in the parent. + * @return A {@code Runnable} that provides an entrypoint into system_server code in the child + * process; {@code null} in the parent. */ private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) { long capabilities = posixCapabilitiesAsBits( - OsConstants.CAP_IPC_LOCK, - OsConstants.CAP_KILL, - OsConstants.CAP_NET_ADMIN, - OsConstants.CAP_NET_BIND_SERVICE, - OsConstants.CAP_NET_BROADCAST, - OsConstants.CAP_NET_RAW, - OsConstants.CAP_SYS_MODULE, - OsConstants.CAP_SYS_NICE, - OsConstants.CAP_SYS_PTRACE, - OsConstants.CAP_SYS_TIME, - OsConstants.CAP_SYS_TTY_CONFIG, - OsConstants.CAP_WAKE_ALARM, - OsConstants.CAP_BLOCK_SUSPEND + OsConstants.CAP_IPC_LOCK, + OsConstants.CAP_KILL, + OsConstants.CAP_NET_ADMIN, + OsConstants.CAP_NET_BIND_SERVICE, + OsConstants.CAP_NET_BROADCAST, + OsConstants.CAP_NET_RAW, + OsConstants.CAP_SYS_MODULE, + OsConstants.CAP_SYS_NICE, + OsConstants.CAP_SYS_PTRACE, + OsConstants.CAP_SYS_TIME, + OsConstants.CAP_SYS_TTY_CONFIG, + OsConstants.CAP_WAKE_ALARM, + OsConstants.CAP_BLOCK_SUSPEND ); /* Containers run without some capabilities, so drop any caps that are not available. */ StructCapUserHeader header = new StructCapUserHeader( @@ -666,38 +672,39 @@ public class ZygoteInit { /* Hardcoded command line to start the system server */ String args[] = { - "--setuid=1000", - "--setgid=1000", - "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1024,1032,1065,3001,3002,3003,3006,3007,3009,3010", - "--capabilities=" + capabilities + "," + capabilities, - "--nice-name=system_server", - "--runtime-args", - "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, - "com.android.server.SystemServer", + "--setuid=1000", + "--setgid=1000", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023," + + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010", + "--capabilities=" + capabilities + "," + capabilities, + "--nice-name=system_server", + "--runtime-args", + "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, + "com.android.server.SystemServer", }; - ZygoteConnection.Arguments parsedArgs = null; + ZygoteArguments parsedArgs = null; int pid; try { - parsedArgs = new ZygoteConnection.Arguments(args); - ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); - ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); + parsedArgs = new ZygoteArguments(args); + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); boolean profileSystemServer = SystemProperties.getBoolean( "dalvik.vm.profilesystemserver", false); if (profileSystemServer) { - parsedArgs.runtimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; + parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } /* Request to fork the system server process */ pid = Zygote.forkSystemServer( - parsedArgs.uid, parsedArgs.gid, - parsedArgs.gids, - parsedArgs.runtimeFlags, + parsedArgs.mUid, parsedArgs.mGid, + parsedArgs.mGids, + parsedArgs.mRuntimeFlags, null, - parsedArgs.permittedCapabilities, - parsedArgs.effectiveCapabilities); + parsedArgs.mPermittedCapabilities, + parsedArgs.mEffectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } @@ -785,10 +792,10 @@ public class ZygoteInit { if (!enableLazyPreload) { bootTimingsTraceLog.traceBegin("ZygotePreload"); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, - SystemClock.uptimeMillis()); + SystemClock.uptimeMillis()); preload(bootTimingsTraceLog); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, - SystemClock.uptimeMillis()); + SystemClock.uptimeMillis()); bootTimingsTraceLog.traceEnd(); // ZygotePreload } else { Zygote.resetNicePriority(); @@ -844,17 +851,16 @@ public class ZygoteInit { /** * Return {@code true} if this device configuration has another zygote. * - * We determine this by comparing the device ABI list with this zygotes - * list. If this zygote supports all ABIs this device supports, there won't - * be another zygote. + * We determine this by comparing the device ABI list with this zygotes list. If this zygote + * supports all ABIs this device supports, there won't be another zygote. */ private static boolean hasSecondZygote(String abiList) { return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList); } private static void waitForSecondaryZygote(String socketName) { - String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ? - Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET; + String otherZygoteName = ZygoteProcess.ZYGOTE_SOCKET_NAME.equals(socketName) + ? ZygoteProcess.ZYGOTE_SECONDARY_SOCKET_NAME : ZygoteProcess.ZYGOTE_SOCKET_NAME; ZygoteProcess.waitForConnectionToZygote(otherZygoteName); } @@ -869,9 +875,8 @@ public class ZygoteInit { } /** - * The main function called when started through the zygote process. This - * could be unified with main(), if the native code in nativeFinishInit() - * were rationalized with Zygote startup.<p> + * The main function called when started through the zygote process. This could be unified with + * main(), if the native code in nativeFinishInit() were rationalized with Zygote startup.<p> * * Current recognized args: * <ul> @@ -881,7 +886,8 @@ public class ZygoteInit { * @param targetSdkVersion target SDK version * @param argv arg strings */ - public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) { + public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, + ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); } @@ -895,9 +901,9 @@ public class ZygoteInit { } /** - * The main function called when starting a child zygote process. This is used as an - * alternative to zygoteInit(), which skips calling into initialization routines that - * start the Binder threadpool. + * The main function called when starting a child zygote process. This is used as an alternative + * to zygoteInit(), which skips calling into initialization routines that start the Binder + * threadpool. */ static final Runnable childZygoteInit( int targetSdkVersion, String[] argv, ClassLoader classLoader) { diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index fecf9b9da5dd..c1bfde194c4e 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -20,14 +20,14 @@ import static android.system.OsConstants.POLLIN; import android.net.LocalServerSocket; import android.net.LocalSocket; -import android.system.Os; import android.system.ErrnoException; +import android.system.Os; import android.system.StructPollfd; import android.util.Log; - import android.util.Slog; -import java.io.IOException; + import java.io.FileDescriptor; +import java.io.IOException; import java.util.ArrayList; /** @@ -63,8 +63,7 @@ class ZygoteServer { */ private boolean mIsForkChild; - ZygoteServer() { - } + ZygoteServer() { } void setForkChild() { mIsForkChild = true; @@ -197,7 +196,7 @@ class ZygoteServer { if (i == 0) { ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); - fds.add(newPeer.getFileDesciptor()); + fds.add(newPeer.getFileDescriptor()); } else { try { ZygoteConnection connection = peers.get(i); diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 9b138ebb760a..7eddcfe425d3 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -16,8 +16,11 @@ #define LOG_TAG "NetUtils" +#include <vector> + #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include "NetdClient.h" #include <utils/misc.h> #include <android_runtime/AndroidRuntime.h> @@ -55,6 +58,31 @@ static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udp static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest); static const uint16_t kDhcpClientPort = 68; +constexpr int MAXPACKETSIZE = 8 * 1024; +// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this. +constexpr int MAXCMDSIZE = 1024; + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + static jclass errnoExceptionClass = + MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException")); + + static jmethodID errnoExceptionCtor = + GetMethodIDOrDie(env, errnoExceptionClass, + "<init>", "(Ljava/lang/String;I)V"); + + jobject exception = env->NewObject(errnoExceptionClass, + errnoExceptionCtor, + detailMessage.get(), + error); + env->Throw(reinterpret_cast<jthrowable>(exception)); +} + static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) { struct sock_filter filter_code[] = { @@ -372,6 +400,63 @@ static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray } } +static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId, + jstring dname, jint ns_class, jint ns_type, jint flags) { + const jsize javaCharsCount = env->GetStringLength(dname); + const jsize byteCountUTF8 = env->GetStringUTFLength(dname); + + // Only allow dname which could be simply formatted to UTF8. + // In native layer, res_mkquery would re-format the input char array to packet. + std::vector<char> queryname(byteCountUTF8 + 1, 0); + + env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data()); + int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkQuery", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId, + jbyteArray msg, jint msgLen, jint flags) { + uint8_t data[MAXCMDSIZE]; + + checkLenAndCopy(env, msg, msgLen, data); + int fd = resNetworkSend(netId, data, msgLen, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkSend", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = jniGetFDFromFileDescriptor(env, javaFd); + int rcode; + std::vector<uint8_t> buf(MAXPACKETSIZE, 0); + + int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + if (res < 0) { + throwErrnoException(env, "resNetworkResult", -res); + return nullptr; + } + + jbyteArray answer = env->NewByteArray(res); + if (answer == nullptr) { + throwErrnoException(env, "resNetworkResult", ENOMEM); + return nullptr; + } else { + env->SetByteArrayRegion(answer, 0, res, + reinterpret_cast<jbyte*>(buf.data())); + } + + return answer; +} // ---------------------------------------------------------------------------- @@ -391,6 +476,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket }, + { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, + { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, + { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 978254175da4..62aa1f38ca30 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -22,10 +22,10 @@ #include <utils/Log.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> +#include <cutils/sched_policy.h> #include <utils/String8.h> #include <utils/Vector.h> #include <processgroup/processgroup.h> -#include <processgroup/sched_policy.h> #include "core_jni_helpers.h" diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 691027dbf83d..43e6399e51fc 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -14,19 +14,32 @@ * limitations under the License. */ +/* + * Disable optimization of this file if we are compiling with the address + * sanitizer. This is a mitigation for b/122921367 and can be removed once the + * bug is fixed. + */ +#if __has_feature(address_sanitizer) +#pragma clang optimize off +#endif + #define LOG_TAG "Zygote" // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc #include <sys/mount.h> #include <linux/fs.h> +#include <array> +#include <atomic> #include <functional> #include <list> #include <optional> #include <sstream> #include <string> +#include <string_view> #include <android/fdsan.h> +#include <arpa/inet.h> #include <fcntl.h> #include <grp.h> #include <inttypes.h> @@ -37,9 +50,11 @@ #include <stdlib.h> #include <sys/capability.h> #include <sys/cdefs.h> +#include <sys/eventfd.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/resource.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> @@ -47,19 +62,20 @@ #include <sys/wait.h> #include <unistd.h> -#include "android-base/logging.h" +#include <android-base/logging.h> #include <android-base/properties.h> #include <android-base/file.h> #include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> #include <cutils/fs.h> #include <cutils/multiuser.h> +#include <cutils/sched_policy.h> #include <private/android_filesystem_config.h> #include <utils/String8.h> #include <selinux/android.h> #include <seccomp_policy.h> #include <stats_event_list.h> #include <processgroup/processgroup.h> -#include <processgroup/sched_policy.h> #include "core_jni_helpers.h" #include <nativehelper/JNIHelp.h> @@ -72,6 +88,9 @@ namespace { +// TODO (chriswailes): Add a function to initialize native Zygote data. +// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces). + using namespace std::placeholders; using android::String8; @@ -82,6 +101,9 @@ using android::base::GetBoolProperty; #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ append(StringPrintf(__VA_ARGS__)) +// This type is duplicated in fd_utils.h +typedef const std::function<void(std::string)>& fail_fn_t; + static pid_t gSystemServerPid = 0; static const char kZygoteClassName[] = "com/android/internal/os/Zygote"; @@ -91,6 +113,152 @@ static jmethodID gCallPostForkChildHooks; static bool g_is_security_enforced = true; +/** + * The maximum number of characters (not including a null terminator) that a + * process name may contain. + */ +static constexpr size_t MAX_NAME_LENGTH = 15; + +/** + * The prefix string for environmental variables storing socket FDs created by + * init. + */ + +static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_"); + +/** + * The file descriptor for the Zygote socket opened by init. + */ + +static int gZygoteSocketFD = -1; + +/** + * The file descriptor for the Blastula pool socket opened by init. + */ + +static int gBlastulaPoolSocketFD = -1; + +/** + * The number of Blastulas currently in this Zygote's pool. + */ +static std::atomic_uint32_t gBlastulaPoolCount = 0; + +/** + * Event file descriptor used to communicate reaped blastulas to the + * ZygoteServer. + */ +static int gBlastulaPoolEventFD = -1; + +/** + * The maximum value that the gBlastulaPoolMax variable may take. This value + * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT + */ +static constexpr int BLASTULA_POOL_MAX_LIMIT = 10; + +/** + * A helper class containing accounting information for Blastulas. + */ +class BlastulaTableEntry { + public: + struct EntryStorage { + int32_t pid; + int32_t read_pipe_fd; + + bool operator!=(const EntryStorage& other) { + return pid != other.pid || read_pipe_fd != other.read_pipe_fd; + } + }; + + private: + static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1}; + + std::atomic<EntryStorage> mStorage; + static_assert(decltype(mStorage)::is_always_lock_free); + + public: + constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {} + + /** + * If the provided PID matches the one stored in this entry, the entry will + * be invalidated and the associated file descriptor will be closed. If the + * PIDs don't match nothing will happen. + * + * @param pid The ID of the process who's entry we want to clear. + * @return True if the entry was cleared; false otherwise + */ + bool ClearForPID(int32_t pid) { + EntryStorage storage = mStorage.load(); + + if (storage.pid == pid) { + /* + * There are three possible outcomes from this compare-and-exchange: + * 1) It succeeds, in which case we close the FD + * 2) It fails and the new value is INVALID_ENTRY_VALUE, in which case + * the entry has already been cleared. + * 3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which + * case the entry has already been cleared and re-used. + * + * In all three cases the goal of the caller has been met and we can + * return true. + */ + if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) { + close(storage.read_pipe_fd); + } + + return true; + } else { + return false; + } + } + + /** + * @return A copy of the data stored in this entry. + */ + std::optional<EntryStorage> GetValues() { + EntryStorage storage = mStorage.load(); + + if (storage != INVALID_ENTRY_VALUE) { + return storage; + } else { + return std::nullopt; + } + } + + /** + * Sets the entry to the given values if it is currently invalid. + * + * @param pid The process ID for the new entry. + * @param read_pipe_fd The read end of the blastula control pipe for this + * process. + * @return True if the entry was set; false otherwise. + */ + bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) { + EntryStorage new_value_storage; + + new_value_storage.pid = pid; + new_value_storage.read_pipe_fd = read_pipe_fd; + + EntryStorage expected = INVALID_ENTRY_VALUE; + + return mStorage.compare_exchange_strong(expected, new_value_storage); + } +}; + +/** + * A table containing information about the Blastulas currently in the pool. + * + * Multiple threads may be attempting to modify the table, either from the + * signal handler or from the ZygoteServer poll loop. Atomic loads/stores in + * the BlastulaTableEntry class prevent data races during these concurrent + * operations. + */ +static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable; + +/** + * The list of open zygote file descriptors. + */ +static FileDescriptorTable* gOpenFdTable = nullptr; + // Must match values in com.android.internal.os.Zygote. enum MountExternalKind { MOUNT_EXTERNAL_NONE = 0, @@ -104,6 +272,9 @@ enum RuntimeFlags : uint32_t { DEBUG_ENABLE_JDWP = 1, }; +// Forward declaration so we don't have to move the signal handler. +static bool RemoveBlastulaTableEntry(pid_t blastula_pid); + static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { std::ostringstream oss; oss << __FILE__ << ":" << line << ": " << msg; @@ -114,6 +285,7 @@ static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { static void SigChldHandler(int /*signal_number*/) { pid_t pid; int status; + int64_t blastulas_removed = 0; // It's necessary to save and restore the errno during this function. // Since errno is stored per thread, changing it here modifies the errno @@ -147,6 +319,11 @@ static void SigChldHandler(int /*signal_number*/) { ALOGE("Exit zygote because system server (%d) has terminated", pid); kill(getpid(), SIGKILL); } + + // Check to see if the PID is in the blastula pool and remove it if it is. + if (RemoveBlastulaTableEntry(pid)) { + ++blastulas_removed; + } } // Note that we shouldn't consider ECHILD an error because @@ -155,6 +332,15 @@ static void SigChldHandler(int /*signal_number*/) { ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno)); } + if (blastulas_removed > 0) { + if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) { + // If this write fails something went terribly wrong. We will now kill + // the zygote and let the system bring it back up. + ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno)); + kill(getpid(), SIGKILL); + } + } + errno = saved_errno; } @@ -179,13 +365,13 @@ static void SetSignalHandlers() { struct sigaction sig_chld = {}; sig_chld.sa_handler = SigChldHandler; - if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) { + if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) { ALOGW("Error setting SIGCHLD handler: %s", strerror(errno)); } struct sigaction sig_hup = {}; sig_hup.sa_handler = SIG_IGN; - if (sigaction(SIGHUP, &sig_hup, NULL) < 0) { + if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) { ALOGW("Error setting SIGHUP handler: %s", strerror(errno)); } } @@ -196,64 +382,57 @@ static void UnsetChldSignalHandler() { memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; - if (sigaction(SIGCHLD, &sa, NULL) < 0) { + if (sigaction(SIGCHLD, &sa, nullptr) < 0) { ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno)); } } // Calls POSIX setgroups() using the int[] object as an argument. -// A NULL argument is tolerated. -static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) { - if (javaGids == NULL) { - return true; +// A nullptr argument is tolerated. +static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) { + if (managed_gids == nullptr) { + return; } - ScopedIntArrayRO gids(env, javaGids); - if (gids.get() == NULL) { - *error_msg = CREATE_ERROR("Getting gids int array failed"); - return false; - } - int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])); - if (rc == -1) { - *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()); - return false; + ScopedIntArrayRO gids(env, managed_gids); + if (gids.get() == nullptr) { + fail_fn(CREATE_ERROR("Getting gids int array failed")); } - return true; + if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) { + fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size())); + } } // Sets the resource limits via setrlimit(2) for the values in the // two-dimensional array of integers that's passed in. The second dimension -// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is +// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is // treated as an empty array. -static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) { - if (javaRlimits == NULL) { - return true; +static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) { + if (managed_rlimits == nullptr) { + return; } rlimit rlim; memset(&rlim, 0, sizeof(rlim)); - for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) { - ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i)); - ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get())); - if (javaRlimit.size() != 3) { - *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3"); - return false; + for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) { + ScopedLocalRef<jobject> + managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i)); + ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get())); + + if (rlimit_handle.size() != 3) { + fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3")); } - rlim.rlim_cur = javaRlimit[1]; - rlim.rlim_max = javaRlimit[2]; + rlim.rlim_cur = rlimit_handle[1]; + rlim.rlim_max = rlimit_handle[2]; - int rc = setrlimit(javaRlimit[0], &rlim); - if (rc == -1) { - *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur, - rlim.rlim_max); - return false; + if (setrlimit(rlimit_handle[0], &rlim) == -1) { + fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", + rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max)); } } - - return true; } static void EnableDebugger() { @@ -313,32 +492,26 @@ static void SetUpSeccompFilter(uid_t uid) { } } -static bool EnableKeepCapabilities(std::string* error_msg) { - int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); - if (rc == -1) { - *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)); - return false; +static void EnableKeepCapabilities(fail_fn_t fail_fn) { + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { + fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno))); } - return true; } -static bool DropCapabilitiesBoundingSet(std::string* error_msg) { - for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) { - int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); - if (rc == -1) { +static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) { + for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {; + if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) { if (errno == EINVAL) { ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify " "your kernel is compiled with file capabilities support"); } else { - *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno))); } } } - return true; } -static bool SetInheritable(uint64_t inheritable, std::string* error_msg) { +static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) { __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; @@ -346,23 +519,19 @@ static bool SetInheritable(uint64_t inheritable, std::string* error_msg) { __user_cap_data_struct capdata[2]; if (capget(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno))); } capdata[0].inheritable = inheritable; capdata[1].inheritable = inheritable >> 32; if (capset(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno))); } - - return true; } -static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable, - std::string* error_msg) { +static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable, + fail_fn_t fail_fn) { __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; @@ -378,27 +547,23 @@ static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inh capdata[1].inheritable = inheritable >> 32; if (capset(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") " - "failed: %s", permitted, effective, inheritable, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") " + "failed: %s", permitted, effective, inheritable, strerror(errno))); } - return true; } -static bool SetSchedulerPolicy(std::string* error_msg) { +static void SetSchedulerPolicy(fail_fn_t fail_fn) { errno = -set_sched_policy(0, SP_DEFAULT); if (errno != 0) { - *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno))); } - return true; } static int UnmountTree(const char* path) { size_t path_len = strlen(path); FILE* fp = setmntent("/proc/mounts", "r"); - if (fp == NULL) { + if (fp == nullptr) { ALOGE("Error opening /proc/mounts: %s", strerror(errno)); return -errno; } @@ -407,7 +572,7 @@ static int UnmountTree(const char* path) { // reverse order to give us the best chance of success. std::list<std::string> toUnmount; mntent* mentry; - while ((mentry = getmntent(fp)) != NULL) { + while ((mentry = getmntent(fp)) != nullptr) { if (strncmp(mentry->mnt_dir, path, path_len) == 0) { toUnmount.push_front(std::string(mentry->mnt_dir)); } @@ -424,8 +589,8 @@ static int UnmountTree(const char* path) { // Create a private mount namespace and bind mount appropriate emulated // storage for the given user. -static bool MountEmulatedStorage(uid_t uid, jint mount_mode, - bool force_mount_namespace, std::string* error_msg) { +static void MountEmulatedStorage(uid_t uid, jint mount_mode, + bool force_mount_namespace, fail_fn_t fail_fn) { // See storage config details at http://source.android.com/tech/storage/ String8 storageSource; @@ -437,44 +602,39 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, storageSource = "/mnt/runtime/write"; } else if (!force_mount_namespace) { // Sane default of no storage visible - return true; + return; } // Create a second private mount namespace for our process if (unshare(CLONE_NEWNS) == -1) { - *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno))); } // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE. if (mount_mode == MOUNT_EXTERNAL_NONE) { - return true; + return; } if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", - NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - storageSource.string(), - strerror(errno)); - return false; + nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s", + storageSource.string(), + strerror(errno))); } // Mount user-specific symlink helper into place userid_t user_id = multiuser_get_user_id(uid); const String8 userSource(String8::format("/mnt/user/%d", user_id)); if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); - return false; + fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)", + userSource.string(), strerror(errno))); } + if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", - NULL, MS_BIND, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", - userSource.string(), - strerror(errno)); - return false; + nullptr, MS_BIND, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s", + userSource.string(), strerror(errno))); } - - return true; } static bool NeedsNoRandomizeWorkaround() { @@ -502,55 +662,45 @@ static bool NeedsNoRandomizeWorkaround() { // descriptor (if any) is closed via dup2(), replacing it with a valid // (open) descriptor to /dev/null. -static bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) { - if (!fdsToClose) { - return true; - } - jsize count = env->GetArrayLength(fdsToClose); - ScopedIntArrayRO ar(env, fdsToClose); - if (ar.get() == NULL) { - *error_msg = "Bad fd array"; - return false; - } - jsize i; - int devnull; - for (i = 0; i < count; i++) { - devnull = open("/dev/null", O_RDWR); - if (devnull < 0) { - *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno)); - return false; +static void DetachDescriptors(JNIEnv* env, + const std::vector<int>& fds_to_close, + fail_fn_t fail_fn) { + + if (fds_to_close.size() > 0) { + android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR)); + if (devnull_fd == -1) { + fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } - ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno)); - if (dup2(devnull, ar[i]) < 0) { - *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno)); - return false; + + for (int fd : fds_to_close) { + ALOGV("Switching descriptor %d to /dev/null", fd); + if (dup2(devnull_fd, fd) == -1) { + fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno))); + } } - close(devnull); } - return true; } -void SetThreadName(const char* thread_name) { +void SetThreadName(const std::string& thread_name) { bool hasAt = false; bool hasDot = false; - const char* s = thread_name; - while (*s) { - if (*s == '.') { + + for (const char str_el : thread_name) { + if (str_el == '.') { hasDot = true; - } else if (*s == '@') { + } else if (str_el == '@') { hasAt = true; } - s++; } - const int len = s - thread_name; - if (len < 15 || hasAt || !hasDot) { - s = thread_name; - } else { - s = thread_name + len - 15; + + const char* name_start_ptr = thread_name.c_str(); + if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) { + name_start_ptr += thread_name.length() - MAX_NAME_LENGTH; } + // pthread_setname_np fails rather than truncating long strings. char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic - strlcpy(buf, s, sizeof(buf)-1); + strlcpy(buf, name_start_ptr, sizeof(buf) - 1); errno = pthread_setname_np(pthread_self(), buf); if (errno != 0) { ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno)); @@ -559,28 +709,16 @@ void SetThreadName(const char* thread_name) { android::base::SetDefaultTag(buf); } -// The list of open zygote file descriptors. -static FileDescriptorTable* gOpenFdTable = NULL; - -static bool FillFileDescriptorVector(JNIEnv* env, - jintArray managed_fds, - std::vector<int>* fds, - std::string* error_msg) { - CHECK(fds != nullptr); - if (managed_fds != nullptr) { - ScopedIntArrayRO ar(env, managed_fds); - if (ar.get() == nullptr) { - *error_msg = "Bad fd array"; - return false; - } - fds->reserve(ar.size()); - for (size_t i = 0; i < ar.size(); ++i) { - fds->push_back(ar[i]); - } - } - return true; -} - +/** + * A failure function used to report fatal errors to the managed runtime. This + * function is often curried with the process name information and then passed + * to called functions. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param msg The error message to be reported + */ [[noreturn]] static void ZygoteFailure(JNIEnv* env, const char* process_name, @@ -601,12 +739,25 @@ static void ZygoteFailure(JNIEnv* env, __builtin_unreachable(); } +/** + * A helper method for converting managed strings to native strings. A fatal + * error is generated if a problem is encountered in extracting a non-null + * string. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param managed_string The managed string to extract + * + * @return An empty option if the managed string is null. A optional-wrapped + * string otherwise. + */ static std::optional<std::string> ExtractJString(JNIEnv* env, const char* process_name, jstring managed_process_name, jstring managed_string) { if (managed_string == nullptr) { - return std::optional<std::string>(); + return std::nullopt; } else { ScopedUtfChars scoped_string_chars(env, managed_string); @@ -618,16 +769,86 @@ static std::optional<std::string> ExtractJString(JNIEnv* env, } } -// Utility routine to fork a zygote. +/** + * A helper method for converting managed integer arrays to native vectors. A + * fatal error is generated if a problem is encountered in extracting a non-null array. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param managed_array The managed integer array to extract + * + * @return An empty option if the managed array is null. A optional-wrapped + * vector otherwise. + */ +static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + jintArray managed_array) { + if (managed_array == nullptr) { + return std::nullopt; + } else { + ScopedIntArrayRO managed_array_handle(env, managed_array); + + if (managed_array_handle.get() != nullptr) { + std::vector<int> native_array; + native_array.reserve(managed_array_handle.size()); + + for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) { + native_array.push_back(managed_array_handle[array_index]); + } + + return std::move(native_array); + + } else { + ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray."); + } + } +} + +/** + * A utility function for blocking signals. + * + * @param signum Signal number to block + * @param fail_fn Fatal error reporting function + * + * @see ZygoteFailure + */ +static void BlockSignal(int signum, fail_fn_t fail_fn) { + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, signum); + + if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) { + fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno))); + } +} + + +/** + * A utility function for unblocking signals. + * + * @param signum Signal number to unblock + * @param fail_fn Fatal error reporting function + * + * @see ZygoteFailure + */ +static void UnblockSignal(int signum, fail_fn_t fail_fn) { + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, signum); + + if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) { + fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno))); + } +} + +// Utility routine to fork a process from the zygote. static pid_t ForkCommon(JNIEnv* env, bool is_system_server, - jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) { + const std::vector<int>& fds_to_close, + const std::vector<int>& fds_to_ignore) { SetSignalHandlers(); - // Block SIGCHLD prior to fork. - sigset_t sigchld; - sigemptyset(&sigchld); - sigaddset(&sigchld, SIGCHLD); - // Curry a failure function. auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", nullptr, _1); @@ -637,9 +858,7 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // This would cause failures because the FDs are not whitelisted. // // Note that the zygote process is single threaded at this point. - if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) { - fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno))); - } + BlockSignal(SIGCHLD, fail_fn); // Close any logging related FDs before we start evaluating the list of // file descriptors. @@ -649,19 +868,10 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // If this is the first fork for this zygote, create the open FD table. If // it isn't, we just need to check whether the list of open files has changed // (and it shouldn't in the normal case). - std::string error_msg; - std::vector<int> fds_to_ignore; - if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) { - fail_fn(error_msg); - } - if (gOpenFdTable == nullptr) { - gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg); - if (gOpenFdTable == nullptr) { - fail_fn(error_msg); - } - } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) { - fail_fn(error_msg); + gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn); + } else { + gOpenFdTable->Restat(fds_to_ignore, fail_fn); } android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level(); @@ -673,24 +883,19 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, PreApplicationInit(); // Clean up any descriptors which must be closed immediately - if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) { - fail_fn(error_msg); - } + DetachDescriptors(env, fds_to_close, fail_fn); // Re-open all remaining open file descriptors so that they aren't shared // with the zygote across a fork. - if (!gOpenFdTable->ReopenOrDetach(&error_msg)) { - fail_fn(error_msg); - } + gOpenFdTable->ReopenOrDetach(fail_fn); // Turn fdsan back on. android_fdsan_set_error_level(fdsan_error_level); } // We blocked SIGCHLD prior to a fork, we unblock it here. - if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) { - fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno))); - } + UnblockSignal(SIGCHLD, fail_fn); + return pid; } @@ -702,32 +907,23 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jstring managed_nice_name, bool is_system_server, bool is_child_zygote, jstring managed_instruction_set, jstring managed_app_data_dir) { - auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", - managed_nice_name, _1); - auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote", - managed_nice_name, _1); + const char* process_name = is_system_server ? "system_server" : "zygote"; + auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1); + auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); auto se_info = extract_fn(managed_se_info); auto nice_name = extract_fn(managed_nice_name); auto instruction_set = extract_fn(managed_instruction_set); auto app_data_dir = extract_fn(managed_app_data_dir); - std::string error_msg; - // Keep capabilities across UID change, unless we're staying root. if (uid != 0) { - if (!EnableKeepCapabilities(&error_msg)) { - fail_fn(error_msg); - } + EnableKeepCapabilities(fail_fn); } - if (!SetInheritable(permitted_capabilities, &error_msg)) { - fail_fn(error_msg); - } + SetInheritable(permitted_capabilities, fail_fn); - if (!DropCapabilitiesBoundingSet(&error_msg)) { - fail_fn(error_msg); - } + DropCapabilitiesBoundingSet(fail_fn); bool use_native_bridge = !is_system_server && instruction_set.has_value() && @@ -744,23 +940,12 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, ALOGW("Native bridge will not be used because managed_app_data_dir == nullptr."); } - if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg)) { - ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno)); - if (errno == ENOTCONN || errno == EROFS) { - // When device is actively encrypting, we get ENOTCONN here - // since FUSE was mounted before the framework restarted. - // When encrypted device is booting, we get EROFS since - // FUSE hasn't been created yet by init. - // In either case, continue without external storage. - } else { - fail_fn(error_msg); - } - } + MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn); // If this zygote isn't root, it won't be able to create a process group, // since the directory is owned by root. if (!is_system_server && getuid() == 0) { - int rc = createProcessGroup(uid, getpid()); + const int rc = createProcessGroup(uid, getpid()); if (rc != 0) { if (rc == -EROFS) { ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?"); @@ -770,13 +955,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - if (!SetGids(env, gids, &error_msg)) { - fail_fn(error_msg); - } - - if (!SetRLimits(env, rlimits, &error_msg)) { - fail_fn(error_msg); - } + SetGids(env, gids, fail_fn); + SetRLimits(env, rlimits, fail_fn); if (use_native_bridge) { // Due to the logic behind use_native_bridge we know that both app_data_dir @@ -835,14 +1015,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, - &error_msg)) { - fail_fn(error_msg); - } + SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn); - if (!SetSchedulerPolicy(&error_msg)) { - fail_fn(error_msg); - } + SetSchedulerPolicy(fail_fn); const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr; const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr; @@ -855,7 +1030,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, // Make it easier to debug audit logs by setting the main thread's name to the // nice name rather than "app_process". if (nice_name.has_value()) { - SetThreadName(nice_name.value().c_str()); + SetThreadName(nice_name.value()); } else if (is_system_server) { SetThreadName("system_server"); } @@ -868,6 +1043,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, if (env->ExceptionCheck()) { fail_fn("Error calling post fork system server hooks."); } + // TODO(oth): Remove hardcoded label here (b/117874058). static const char* kSystemServerLabel = "u:r:system_server:s0"; if (selinux_android_setcon(kSystemServerLabel) != 0) { @@ -971,6 +1147,74 @@ static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gi return capabilities & GetEffectiveCapabilityMask(env); } + +/** + * Adds the given information about a newly created blastula to the Zygote's + * blastula table. + * + * @param blastula_pid Process ID of the newly created blastula + * @param read_pipe_fd File descriptor for the read end of the blastula + * reporting pipe. Used in the ZygoteServer poll loop to track blastula + * specialization. + */ +static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) { + static int sBlastulaTableInsertIndex = 0; + + int search_index = sBlastulaTableInsertIndex; + + do { + if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) { + // Start our next search right after where we finished this one. + sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size(); + + return; + } + + search_index = (search_index + 1) % gBlastulaTable.size(); + } while (search_index != sBlastulaTableInsertIndex); + + // Much like money in the banana stand, there should always be an entry + // in the blastula table. + __builtin_unreachable(); +} + +/** + * Invalidates the entry in the BlastulaTable corresponding to the provided + * process ID if it is present. If an entry was removed the blastula pool + * count is decremented. + * + * @param blastula_pid Process ID of the blastula entry to invalidate + * @return True if an entry was invalidated; false otherwise + */ +static bool RemoveBlastulaTableEntry(pid_t blastula_pid) { + for (BlastulaTableEntry& entry : gBlastulaTable) { + if (entry.ClearForPID(blastula_pid)) { + --gBlastulaPoolCount; + return true; + } + } + + return false; +} + +/** + * @return A vector of the read pipe FDs for each of the active blastulas. + */ +std::vector<int> MakeBlastulaPipeReadFDVector() { + std::vector<int> fd_vec; + fd_vec.reserve(gBlastulaTable.size()); + + for (BlastulaTableEntry& entry : gBlastulaTable) { + auto entry_values = entry.GetValues(); + + if (entry_values.has_value()) { + fd_vec.push_back(entry_values.value().read_pipe_fd); + } + } + + return fd_vec; +} + } // anonymous namespace namespace android { @@ -989,11 +1233,34 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, - jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, + jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); + if (UNLIKELY(managed_fds_to_close == nullptr)) { + ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector."); + } + + std::vector<int> fds_to_close = + ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value(); + std::vector<int> fds_to_ignore = + ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore) + .value_or(std::vector<int>()); + + std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector(); + + fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end()); + fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end()); + +// fds_to_close.push_back(gBlastulaPoolSocketFD); + + if (gBlastulaPoolEventFD != -1) { + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + } + pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore); + if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, @@ -1007,9 +1274,19 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { + std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), + fds_to_ignore(fds_to_close); + +// fds_to_close.push_back(gBlastulaPoolSocketFD); + + if (gBlastulaPoolEventFD != -1) { + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + } + pid_t pid = ForkCommon(env, true, - /* managed_fds_to_close= */ nullptr, - /* managed_fds_to_ignore= */ nullptr); + fds_to_close, + fds_to_ignore); if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities, @@ -1043,6 +1320,52 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( return pid; } +/** + * A JNI function that forks a blastula from the Zygote while ensuring proper + * file descriptor hygiene. + * + * @param env Managed runtime environment + * @param read_pipe_fd The read FD for the blastula reporting pipe. Manually closed by blastlas + * in managed code. + * @param write_pipe_fd The write FD for the blastula reporting pipe. Manually closed by the + * zygote in managed code. + * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by + * the FD hygiene code and automatically "closed" in the new blastula. + * @return + */ +static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass, + jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) { + std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), + fds_to_ignore(fds_to_close); + + std::vector<int> session_socket_fds = + ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds) + .value_or(std::vector<int>()); + + // The Blastula Pool Event FD is created during the initialization of the + // blastula pool and should always be valid here. + + fds_to_close.push_back(gZygoteSocketFD); + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end()); + + fds_to_ignore.push_back(gZygoteSocketFD); + fds_to_ignore.push_back(gBlastulaPoolSocketFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(read_pipe_fd); + fds_to_ignore.push_back(write_pipe_fd); + fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end()); + + pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore); + + if (blastula_pid != 0) { + ++gBlastulaPoolCount; + AddBlastulaTableEntry(blastula_pid, read_pipe_fd); + } + + return blastula_pid; +} + static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( JNIEnv* env, jclass, jstring path) { ScopedUtfChars path_native(env, path); @@ -1091,6 +1414,119 @@ static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* en UnmountTree("/storage"); } +/** + * Called from a blastula to specialize the process for a specific application. + * + * @param env Managed runtime environment + * @param uid User ID of the new application + * @param gid Group ID of the new application + * @param gids Extra groups that the process belongs to + * @param runtime_flags Flags for changing the behavior of the managed runtime + * @param rlimits Resource limits + * @param mount_external The mode (read/write/normal) that external storage will be mounted with + * @param se_info SELinux policy information + * @param nice_name New name for this process + * @param is_child_zygote If the process is to become a WebViewZygote + * @param instruction_set The instruction set expected/requested by the new application + * @param app_data_dir Path to the application's data directory + */ +static void com_android_internal_os_Zygote_nativeSpecializeBlastula( + JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, + jint runtime_flags, jobjectArray rlimits, + jint mount_external, jstring se_info, jstring nice_name, + jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { + jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); + + SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, + capabilities, capabilities, + mount_external, se_info, nice_name, false, + is_child_zygote == JNI_TRUE, instruction_set, app_data_dir); +} + +/** + * A helper method for fetching socket file descriptors that were opened by init from the + * environment. + * + * @param env Managed runtime environment + * @param is_primary If this process is the primary or secondary Zygote; used to compute the name + * of the environment variable storing the file descriptors. + */ +static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass, + jboolean is_primary) { + std::string android_socket_prefix(ANDROID_SOCKET_PREFIX); + std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary"); + char* env_var_val = getenv(env_var_name.c_str()); + + if (env_var_val != nullptr) { + gZygoteSocketFD = atoi(env_var_val); + ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD); + } else { + ALOGE("Unable to fetch Zygote socket file descriptor"); + } + + env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary"); + env_var_val = getenv(env_var_name.c_str()); + + if (env_var_val != nullptr) { + gBlastulaPoolSocketFD = atoi(env_var_val); + ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD); + } else { + ALOGE("Unable to fetch Blastula pool socket file descriptor"); + } +} + +/** + * @param env Managed runtime environment + * @return A managed array of raw file descriptors for the read ends of the blastula reporting + * pipes. + */ +static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) { + std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector(); + + jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size()); + env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data()); + + return managed_blastula_fds; +} + +/** + * A JNI wrapper around RemoveBlastulaTableEntry. + * + * @param env Managed runtime environment + * @param blastula_pid Process ID of the blastula entry to invalidate + * @return True if an entry was invalidated; false otherwise. + */ +static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass, + jint blastula_pid) { + return RemoveBlastulaTableEntry(blastula_pid); +} + +/** + * Creates the blastula pool event FD if it doesn't exist and returns it. This is used by the + * ZygoteServer poll loop to know when to re-fill the blastula pool. + * + * @param env Managed runtime environment + * @return A raw event file descriptor used to communicate (from the signal handler) when the + * Zygote receives a SIGCHLD for a blastula + */ +static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) { + if (gBlastulaPoolEventFD == -1) { + if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) { + ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno))); + } + } + + return gBlastulaPoolEventFD; +} + +/** + * @param env Managed runtime environment + * @return The number of blastulas currently in the blastula pool + */ +static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) { + return gBlastulaPoolCount; +} + static const JNINativeMethod gMethods[] = { { "nativeSecurityInit", "()V", (void *) com_android_internal_os_Zygote_nativeSecurityInit }, @@ -1104,7 +1540,22 @@ static const JNINativeMethod gMethods[] = { { "nativeUnmountStorageOnInit", "()V", (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit }, { "nativePreApplicationInit", "()V", - (void *) com_android_internal_os_Zygote_nativePreApplicationInit } + (void *) com_android_internal_os_Zygote_nativePreApplicationInit }, + { "nativeForkBlastula", "(II[I)I", + (void *) com_android_internal_os_Zygote_nativeForkBlastula }, + { "nativeSpecializeBlastula", + "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", + (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula }, + { "nativeGetSocketFDs", "(Z)V", + (void *) com_android_internal_os_Zygote_nativeGetSocketFDs }, + { "nativeGetBlastulaPipeFDs", "()[I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs }, + { "nativeRemoveBlastulaTableEntry", "(I)Z", + (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry }, + { "nativeGetBlastulaPoolEventFD", "()I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD }, + { "nativeGetBlastulaPoolCount", "()I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount } }; int register_com_android_internal_os_Zygote(JNIEnv* env) { diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index c8e4125efb15..0ed8c0c97738 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -37,6 +37,8 @@ static const char* kPathWhitelist[] = { "/dev/null", "/dev/socket/zygote", "/dev/socket/zygote_secondary", + "/dev/socket/blastula_pool", + "/dev/socket/blastula_pool_secondary", "/dev/socket/webview_zygote", "/dev/socket/heapprofd", "/sys/kernel/debug/tracing/trace_marker", @@ -129,15 +131,14 @@ FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr; // open zygote file descriptor. class FileDescriptorInfo { public: - // Create a FileDescriptorInfo for a given file descriptor. Returns - // |NULL| if an error occurred. - static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg); + // Create a FileDescriptorInfo for a given file descriptor. + static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn); // Checks whether the file descriptor associated with this object // refers to the same description. - bool Restat() const; + bool RefersToSameFile() const; - bool ReopenOrDetach(std::string* error_msg) const; + void ReopenOrDetach(fail_fn_t fail_fn) const; const int fd; const struct stat stat; @@ -163,19 +164,18 @@ class FileDescriptorInfo { // address). static bool GetSocketName(const int fd, std::string* result); - bool DetachSocket(std::string* error_msg) const; + void DetachSocket(fail_fn_t fail_fn) const; DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); }; // static -FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) { +FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) { struct stat f_stat; // This should never happen; the zygote should always have the right set // of permissions required to stat all its open files. if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { - *error_msg = android::base::StringPrintf("Unable to stat %d", fd); - return nullptr; + fail_fn(android::base::StringPrintf("Unable to stat %d", fd)); } const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get(); @@ -183,15 +183,13 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ if (S_ISSOCK(f_stat.st_mode)) { std::string socket_name; if (!GetSocketName(fd, &socket_name)) { - *error_msg = "Unable to get socket name"; - return nullptr; + fail_fn("Unable to get socket name"); } if (!whitelist->IsAllowed(socket_name)) { - *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)", - socket_name.c_str(), - fd); - return nullptr; + fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)", + socket_name.c_str(), + fd)); } return new FileDescriptorInfo(fd); @@ -204,26 +202,35 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). // S_ISLINK : Not supported. // S_ISBLK : Not supported. - // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate - // with the child process across forks but those should have been closed - // before we got to this point. + // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to + // communicate with the child processes across forks but those should have been + // added to the redirection exemption list. if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { - *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode); - return nullptr; + std::string mode = "Unknown"; + + if (S_ISDIR(f_stat.st_mode)) { + mode = "DIR"; + } else if (S_ISLNK(f_stat.st_mode)) { + mode = "LINK"; + } else if (S_ISBLK(f_stat.st_mode)) { + mode = "BLOCK"; + } else if (S_ISFIFO(f_stat.st_mode)) { + mode = "FIFO"; + } + + fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d: %s", fd, mode.c_str())); } std::string file_path; const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd); if (!android::base::Readlink(fd_path, &file_path)) { - *error_msg = android::base::StringPrintf("Could not read fd link %s: %s", - fd_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Could not read fd link %s: %s", + fd_path.c_str(), + strerror(errno))); } if (!whitelist->IsAllowed(file_path)) { - *error_msg = std::string("Not whitelisted : ").append(file_path); - return nullptr; + fail_fn(std::string("Not whitelisted : ").append(file_path)); } // File descriptor flags : currently on FD_CLOEXEC. We can set these @@ -231,11 +238,10 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // there won't be any races. const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); if (fd_flags == -1) { - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s", - fd, - file_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s", + fd, + file_path.c_str(), + strerror(errno))); } // File status flags : @@ -252,11 +258,10 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // their presence and pass them in to open(). int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); if (fs_flags == -1) { - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s", - fd, - file_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s", + fd, + file_path.c_str(), + strerror(errno))); } // File offset : Ignore the offset for non seekable files. @@ -271,7 +276,7 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); } -bool FileDescriptorInfo::Restat() const { +bool FileDescriptorInfo::RefersToSameFile() const { struct stat f_stat; if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { PLOG(ERROR) << "Unable to restat fd " << fd; @@ -281,9 +286,9 @@ bool FileDescriptorInfo::Restat() const { return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; } -bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const { +void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const { if (is_sock) { - return DetachSocket(error_msg); + return DetachSocket(fail_fn); } // NOTE: This might happen if the file was unlinked after being opened. @@ -292,57 +297,50 @@ bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const { const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); if (new_fd == -1) { - *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s", - file_path.c_str(), - open_flags, - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s", + file_path.c_str(), + open_flags, + strerror(errno))); } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s", - new_fd, - fd_flags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s", + new_fd, + fd_flags, + file_path.c_str(), + strerror(errno))); } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s", - new_fd, - fs_flags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s", + new_fd, + fs_flags, + file_path.c_str(), + strerror(errno))); } if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s", - new_fd, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s", + new_fd, + file_path.c_str(), + strerror(errno))); } - int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0; - if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) { + int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0; + if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s", - fd, - new_fd, - dupFlags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s", + fd, + new_fd, + dup_flags, + file_path.c_str(), + strerror(errno))); } close(new_fd); - - return true; } FileDescriptorInfo::FileDescriptorInfo(int fd) : @@ -368,7 +366,6 @@ FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file is_sock(false) { } -// static bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { sockaddr_storage ss; sockaddr* addr = reinterpret_cast<sockaddr*>(&ss); @@ -412,86 +409,75 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { return true; } -bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const { +void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const { const int dev_null_fd = open("/dev/null", O_RDWR); if (dev_null_fd < 0) { - *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno)); - return false; + fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } if (dup2(dev_null_fd, fd) == -1) { - *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", - fd, - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", + fd, + strerror(errno))); } if (close(dev_null_fd) == -1) { - *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno))); } - - return true; } // static FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore, - std::string* error_msg) { - DIR* d = opendir(kFdPath); - if (d == nullptr) { - *error_msg = std::string("Unable to open directory ").append(kFdPath); - return nullptr; + fail_fn_t fail_fn) { + DIR* proc_fd_dir = opendir(kFdPath); + if (proc_fd_dir == nullptr) { + fail_fn(std::string("Unable to open directory ").append(kFdPath)); } - int dir_fd = dirfd(d); - dirent* e; + + int dir_fd = dirfd(proc_fd_dir); + dirent* dir_entry; std::unordered_map<int, FileDescriptorInfo*> open_fd_map; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); + while ((dir_entry = readdir(proc_fd_dir)) != nullptr) { + const int fd = ParseFd(dir_entry, dir_fd); if (fd == -1) { continue; } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { LOG(INFO) << "Ignoring open file descriptor " << fd; continue; } - FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg); - if (info == NULL) { - if (closedir(d) == -1) { - PLOG(ERROR) << "Unable to close directory"; - } - return NULL; - } - open_fd_map[fd] = info; + open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn); } - if (closedir(d) == -1) { - *error_msg = "Unable to close directory"; - return nullptr; + if (closedir(proc_fd_dir) == -1) { + fail_fn("Unable to close directory"); } + return new FileDescriptorTable(open_fd_map); } -bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) { +void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) { std::set<int> open_fds; // First get the list of open descriptors. - DIR* d = opendir(kFdPath); - if (d == NULL) { - *error_msg = android::base::StringPrintf("Unable to open directory %s: %s", - kFdPath, - strerror(errno)); - return false; + DIR* proc_fd_dir = opendir(kFdPath); + if (proc_fd_dir == nullptr) { + fail_fn(android::base::StringPrintf("Unable to open directory %s: %s", + kFdPath, + strerror(errno))); } - int dir_fd = dirfd(d); - dirent* e; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); + int dir_fd = dirfd(proc_fd_dir); + dirent* dir_entry; + while ((dir_entry = readdir(proc_fd_dir)) != nullptr) { + const int fd = ParseFd(dir_entry, dir_fd); if (fd == -1) { continue; } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { LOG(INFO) << "Ignoring open file descriptor " << fd; continue; @@ -500,27 +486,24 @@ bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::str open_fds.insert(fd); } - if (closedir(d) == -1) { - *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno)); - return false; + if (closedir(proc_fd_dir) == -1) { + fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno))); } - return RestatInternal(open_fds, error_msg); + RestatInternal(open_fds, fail_fn); } -// Reopens all file descriptors that are contained in the table. Returns true -// if all descriptors were successfully re-opened or detached, and false if an -// error occurred. -bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) { +// Reopens all file descriptors that are contained in the table. +void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) { std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { const FileDescriptorInfo* info = it->second; - if (info == NULL || !info->ReopenOrDetach(error_msg)) { - return false; + if (info == nullptr) { + return; + } else { + info->ReopenOrDetach(fail_fn); } } - - return true; } FileDescriptorTable::FileDescriptorTable( @@ -528,9 +511,7 @@ FileDescriptorTable::FileDescriptorTable( : open_fd_map_(map) { } -bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) { - bool error = false; - +void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) { // Iterate through the list of file descriptors we've already recorded // and check whether : // @@ -553,28 +534,18 @@ bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* e } else { // The entry from the file descriptor table is still open. Restat // it and check whether it refers to the same file. - const bool same_file = it->second->Restat(); - if (!same_file) { + if (!it->second->RefersToSameFile()) { // The file descriptor refers to a different description. We must // update our entry in the table. delete it->second; - it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg); - if (it->second == NULL) { - // The descriptor no longer no longer refers to a whitelisted file. - // We flag an error and remove it from the list of files we're - // tracking. - error = true; - it = open_fd_map_.erase(it); - } else { - // Successfully restatted the file, move on to the next open FD. - ++it; - } + it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn); } else { // It's the same file. Nothing to do here. Move on to the next open // FD. - ++it; } + ++it; + // Finally, remove the FD from the set of open_fds. We do this last because // |element| will not remain valid after a call to erase. open_fds.erase(element); @@ -593,25 +564,15 @@ bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* e std::set<int>::const_iterator it; for (it = open_fds.begin(); it != open_fds.end(); ++it) { const int fd = (*it); - FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg); - if (info == NULL) { - // A newly opened file is not on the whitelist. Flag an error and - // continue. - error = true; - } else { - // Track the newly opened file. - open_fd_map_[fd] = info; - } + open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn); } } - - return !error; } // static -int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) { +int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) { char* end; - const int fd = strtol(e->d_name, &end, 10); + const int fd = strtol(dir_entry->d_name, &end, 10); if ((*end) != '\0') { return -1; } diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h index 09022a2e2408..2caf1575981a 100644 --- a/core/jni/fd_utils.h +++ b/core/jni/fd_utils.h @@ -30,6 +30,9 @@ class FileDescriptorInfo; +// This type is duplicated in com_android_internal_os_Zygote.cpp +typedef const std::function<void(std::string)>& fail_fn_t; + // Whitelist of open paths that the zygote is allowed to keep open. // // In addition to the paths listed in kPathWhitelist in file_utils.cpp, and @@ -76,19 +79,19 @@ class FileDescriptorTable { // /proc/self/fd for the list of open file descriptors and collects // information about them. Returns NULL if an error occurs. static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore, - std::string* error_msg); + fail_fn_t fail_fn); - bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg); + void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn); // Reopens all file descriptors that are contained in the table. Returns true // if all descriptors were successfully re-opened or detached, and false if an // error occurred. - bool ReopenOrDetach(std::string* error_msg); + void ReopenOrDetach(fail_fn_t fail_fn); private: explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map); - bool RestatInternal(std::set<int>& open_fds, std::string* error_msg); + void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn); static int ParseFd(dirent* e, int dir_fd); diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto index b70bb677d7a0..21717d886266 100644 --- a/core/proto/android/server/connectivity/data_stall_event.proto +++ b/core/proto/android/server/connectivity/data_stall_event.proto @@ -41,7 +41,7 @@ enum RadioTech { RADIO_TECHNOLOGY_UMTS = 3; RADIO_TECHNOLOGY_IS95A = 4; RADIO_TECHNOLOGY_IS95B = 5; - RADIO_TECHNOLOGY_1xRTT = 6; + RADIO_TECHNOLOGY_1XRTT = 6; RADIO_TECHNOLOGY_EVDO_0 = 7; RADIO_TECHNOLOGY_EVDO_A = 8; RADIO_TECHNOLOGY_HSDPA = 9; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8ea2ab7239ab..75879860b2c5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -122,6 +122,9 @@ be sent during a change to the audio output device. --> <bool name="config_sendAudioBecomingNoisy">true</bool> + <!-- Whether Hearing Aid profile is supported --> + <bool name="config_hearing_aid_profile_supported">true</bool> + <!-- Flag to disable all transition animations --> <bool name="config_disableTransitionAnimation">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 310aaf4bce8a..8d832ca0227b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -434,6 +434,7 @@ <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" /> <java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" /> <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" /> + <java-symbol type="bool" name="config_hearing_aid_profile_supported" /> <java-symbol type="integer" name="config_cursorWindowSize" /> <java-symbol type="integer" name="config_drawLockTimeoutMillis" /> <java-symbol type="integer" name="config_doublePressOnPowerBehavior" /> diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java new file mode 100644 index 000000000000..941cfdb20fed --- /dev/null +++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.test.filters; + +import android.os.Bundle; + +import com.android.test.filters.SelectTest; + +/** + * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests. + * + * <p>Use this filter when running FrameworksCoreTests as + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.server.wm.test.filters.CoreTestsFilter \ + * -e selectTest_verbose true \ + * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner + * </pre> + */ +public final class CoreTestsFilter extends SelectTest { + + private static final String[] SELECTED_CORE_TESTS = { + "android.app.servertransaction.", // all tests under the package. + "android.view.DisplayCutoutTest", + }; + + public CoreTestsFilter(Bundle testArgs) { + super(addSelectTest(testArgs, SELECTED_CORE_TESTS)); + } +} diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt new file mode 100644 index 000000000000..4b2331db7c34 --- /dev/null +++ b/jarjar_rules_hidl.txt @@ -0,0 +1 @@ +rule android.hidl.** android.internal.hidl.@1 diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 2f7d599c68bf..a2da0a079550 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -24,7 +24,7 @@ java_library { ":services-networkstack-shared-srcs", ], static_libs: [ - "dhcp-packet-lib", + "services-netlink-lib", ] } diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 7f8bb93ae023..5ab833bda66d 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -28,6 +28,7 @@ <!-- Launch captive portal app as specific user --> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.NETWORK_STACK" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" diff --git a/services/net/java/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 494395285f5b..50c4dfc8d700 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -16,10 +16,6 @@ package android.net.apf; -import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION; import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ARPHRD_ETHER; @@ -35,6 +31,10 @@ import static com.android.internal.util.BitUtils.getUint16; import static com.android.internal.util.BitUtils.getUint32; import static com.android.internal.util.BitUtils.getUint8; import static com.android.internal.util.BitUtils.uint32; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -46,7 +46,7 @@ import android.net.LinkProperties; import android.net.NetworkUtils; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; -import android.net.ip.IpClientCallbacks; +import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; import android.net.metrics.IpConnectivityLog; @@ -337,7 +337,7 @@ public class ApfFilter { private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; private final ApfCapabilities mApfCapabilities; - private final IpClientCallbacks mIpClientCallback; + private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; private final IpConnectivityLog mMetricsLog; @@ -378,7 +378,7 @@ public class ApfFilter { @VisibleForTesting ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacks ipClientCallback, IpConnectivityLog log) { + IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) { mApfCapabilities = config.apfCapabilities; mIpClientCallback = ipClientCallback; mInterfaceParams = ifParams; @@ -1420,7 +1420,7 @@ public class ApfFilter { * filtering using APF programs. */ public static ApfFilter maybeCreate(Context context, ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacks ipClientCallback) { + InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) { if (context == null || config == null || ifParams == null) return null; ApfCapabilities apfCapabilities = config.apfCapabilities; if (apfCapabilities == null) return null; diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java index 87a1b5ea8b4d..87a1b5ea8b4d 100644 --- a/services/net/java/android/net/apf/ApfGenerator.java +++ b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java index b2eb4e21b591..b2eb4e21b591 100644 --- a/services/net/java/android/net/dhcp/DhcpAckPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java new file mode 100644 index 000000000000..04ac9a301813 --- /dev/null +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -0,0 +1,1052 @@ +/* + * 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.dhcp; + +import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; +import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; +import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; +import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_MTU; +import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; +import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; +import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; +import static android.net.dhcp.DhcpPacket.INADDR_ANY; +import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; +import static android.net.util.SocketUtils.makePacketSocketAddress; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ETH_P_IP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_RAW; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_BROADCAST; +import static android.system.OsConstants.SO_RCVBUF; +import static android.system.OsConstants.SO_REUSEADDR; + +import android.content.Context; +import android.net.DhcpResults; +import android.net.NetworkUtils; +import android.net.TrafficStats; +import android.net.ip.IpClient; +import android.net.metrics.DhcpClientEvent; +import android.net.metrics.DhcpErrorEvent; +import android.net.metrics.IpConnectivityLog; +import android.net.util.InterfaceParams; +import android.net.util.SocketUtils; +import android.os.Message; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.system.Os; +import android.util.EventLog; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.HexDump; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +/** + * A DHCPv4 client. + * + * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android + * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine. + * + * TODO: + * + * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour). + * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not + * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a + * given SSID), it requests the last-leased IP address on the same interface, causing a delay if + * the server NAKs or a timeout if it doesn't. + * + * Known differences from current behaviour: + * + * - Does not request the "static routes" option. + * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now. + * - Requests the "broadcast" option, but does nothing with it. + * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0). + * + * @hide + */ +public class DhcpClient extends StateMachine { + + private static final String TAG = "DhcpClient"; + private static final boolean DBG = true; + private static final boolean STATE_DBG = false; + private static final boolean MSG_DBG = false; + private static final boolean PACKET_DBG = false; + + // Timers and timeouts. + private static final int SECONDS = 1000; + private static final int FIRST_TIMEOUT_MS = 2 * SECONDS; + private static final int MAX_TIMEOUT_MS = 128 * SECONDS; + + // This is not strictly needed, since the client is asynchronous and implements exponential + // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was + // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at + // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. + private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; + + // DhcpClient uses IpClient's handler. + private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; + + /* Commands from controller to start/stop DHCP */ + public static final int CMD_START_DHCP = PUBLIC_BASE + 1; + public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; + + /* Notification from DHCP state machine prior to DHCP discovery/renewal */ + public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3; + /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates + * success/failure */ + public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4; + /* Notification from DHCP state machine before quitting */ + public static final int CMD_ON_QUIT = PUBLIC_BASE + 5; + + /* Command from controller to indicate DHCP discovery/renewal can continue + * after pre DHCP action is complete */ + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6; + + /* Command and event notification to/from IpManager requesting the setting + * (or clearing) of an IPv4 LinkAddress. + */ + public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7; + public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8; + public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9; + + /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ + public static final int DHCP_SUCCESS = 1; + public static final int DHCP_FAILURE = 2; + + // Internal messages. + private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100; + private static final int CMD_KICK = PRIVATE_BASE + 1; + private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; + private static final int CMD_TIMEOUT = PRIVATE_BASE + 3; + private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4; + private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5; + private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6; + + // For message logging. + private static final Class[] sMessageClasses = { DhcpClient.class }; + private static final SparseArray<String> sMessageNames = + MessageUtils.findMessageNames(sMessageClasses); + + // DHCP parameters that we request. + /* package */ static final byte[] REQUESTED_PARAMS = new byte[] { + DHCP_SUBNET_MASK, + DHCP_ROUTER, + DHCP_DNS_SERVER, + DHCP_DOMAIN_NAME, + DHCP_MTU, + DHCP_BROADCAST_ADDRESS, // TODO: currently ignored. + DHCP_LEASE_TIME, + DHCP_RENEWAL_TIME, + DHCP_REBINDING_TIME, + DHCP_VENDOR_INFO, + }; + + // DHCP flag that means "yes, we support unicast." + private static final boolean DO_UNICAST = false; + + // System services / libraries we use. + private final Context mContext; + private final Random mRandom; + private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); + + // Sockets. + // - We use a packet socket to receive, because servers send us packets bound for IP addresses + // which we have not yet configured, and the kernel protocol stack drops these. + // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can + // be off-link as well as on-link). + private FileDescriptor mPacketSock; + private FileDescriptor mUdpSock; + private ReceiveThread mReceiveThread; + + // State variables. + private final StateMachine mController; + private final WakeupMessage mKickAlarm; + private final WakeupMessage mTimeoutAlarm; + private final WakeupMessage mRenewAlarm; + private final WakeupMessage mRebindAlarm; + private final WakeupMessage mExpiryAlarm; + private final String mIfaceName; + + private boolean mRegisteredForPreDhcpNotification; + private InterfaceParams mIface; + // TODO: MacAddress-ify more of this class hierarchy. + private byte[] mHwAddr; + private SocketAddress mInterfaceBroadcastAddr; + private int mTransactionId; + private long mTransactionStartMillis; + private DhcpResults mDhcpLease; + private long mDhcpLeaseExpiry; + private DhcpResults mOffer; + + // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. + private long mLastInitEnterTime; + private long mLastBoundExitTime; + + // States. + private State mStoppedState = new StoppedState(); + private State mDhcpState = new DhcpState(); + private State mDhcpInitState = new DhcpInitState(); + private State mDhcpSelectingState = new DhcpSelectingState(); + private State mDhcpRequestingState = new DhcpRequestingState(); + private State mDhcpHaveLeaseState = new DhcpHaveLeaseState(); + private State mConfiguringInterfaceState = new ConfiguringInterfaceState(); + private State mDhcpBoundState = new DhcpBoundState(); + private State mDhcpRenewingState = new DhcpRenewingState(); + private State mDhcpRebindingState = new DhcpRebindingState(); + private State mDhcpInitRebootState = new DhcpInitRebootState(); + private State mDhcpRebootingState = new DhcpRebootingState(); + private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); + private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState); + + private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { + cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName; + return new WakeupMessage(mContext, getHandler(), cmdName, cmd); + } + + // TODO: Take an InterfaceParams instance instead of an interface name String. + private DhcpClient(Context context, StateMachine controller, String iface) { + super(TAG, controller.getHandler()); + + mContext = context; + mController = controller; + mIfaceName = iface; + + addState(mStoppedState); + addState(mDhcpState); + addState(mDhcpInitState, mDhcpState); + addState(mWaitBeforeStartState, mDhcpState); + addState(mDhcpSelectingState, mDhcpState); + addState(mDhcpRequestingState, mDhcpState); + addState(mDhcpHaveLeaseState, mDhcpState); + addState(mConfiguringInterfaceState, mDhcpHaveLeaseState); + addState(mDhcpBoundState, mDhcpHaveLeaseState); + addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState); + addState(mDhcpRenewingState, mDhcpHaveLeaseState); + addState(mDhcpRebindingState, mDhcpHaveLeaseState); + addState(mDhcpInitRebootState, mDhcpState); + addState(mDhcpRebootingState, mDhcpState); + + setInitialState(mStoppedState); + + mRandom = new Random(); + + // Used to schedule packet retransmissions. + mKickAlarm = makeWakeupMessage("KICK", CMD_KICK); + // Used to time out PacketRetransmittingStates. + mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT); + // Used to schedule DHCP reacquisition. + mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); + mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); + mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); + } + + public void registerForPreDhcpNotification() { + mRegisteredForPreDhcpNotification = true; + } + + public static DhcpClient makeDhcpClient( + Context context, StateMachine controller, InterfaceParams ifParams) { + DhcpClient client = new DhcpClient(context, controller, ifParams.name); + client.mIface = ifParams; + client.start(); + return client; + } + + private boolean initInterface() { + if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName); + if (mIface == null) { + Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName); + return false; + } + + mHwAddr = mIface.macAddr.toByteArray(); + mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST); + return true; + } + + private void startNewTransaction() { + mTransactionId = mRandom.nextInt(); + mTransactionStartMillis = SystemClock.elapsedRealtime(); + } + + private boolean initSockets() { + return initPacketSocket() && initUdpSocket(); + } + + private boolean initPacketSocket() { + try { + mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); + SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index); + Os.bind(mPacketSock, addr); + NetworkUtils.attachDhcpFilter(mPacketSock); + } catch(SocketException|ErrnoException e) { + Log.e(TAG, "Error creating packet socket", e); + return false; + } + return true; + } + + private boolean initUdpSocket() { + final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP); + try { + mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); + Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); + } catch(SocketException|ErrnoException e) { + Log.e(TAG, "Error creating UDP socket", e); + return false; + } finally { + TrafficStats.setThreadStatsTag(oldTag); + } + return true; + } + + private boolean connectUdpSock(Inet4Address to) { + try { + Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER); + return true; + } catch (SocketException|ErrnoException e) { + Log.e(TAG, "Error connecting UDP socket", e); + return false; + } + } + + private static void closeQuietly(FileDescriptor fd) { + try { + IoBridge.closeAndSignalBlockedThreads(fd); + } catch (IOException ignored) {} + } + + private void closeSockets() { + closeQuietly(mUdpSock); + closeQuietly(mPacketSock); + } + + class ReceiveThread extends Thread { + + private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH]; + private volatile boolean mStopped = false; + + public void halt() { + mStopped = true; + closeSockets(); // Interrupts the read() call the thread is blocked in. + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "Receive thread started"); + while (!mStopped) { + int length = 0; // Or compiler can't tell it's initialized if a parse error occurs. + try { + length = Os.read(mPacketSock, mPacket, 0, mPacket.length); + DhcpPacket packet = null; + packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2); + if (DBG) Log.d(TAG, "Received packet: " + packet); + sendMessage(CMD_RECEIVED_PACKET, packet); + } catch (IOException|ErrnoException e) { + if (!mStopped) { + Log.e(TAG, "Read error", e); + logError(DhcpErrorEvent.RECEIVE_ERROR); + } + } catch (DhcpPacket.ParseException e) { + Log.e(TAG, "Can't parse packet: " + e.getMessage()); + if (PACKET_DBG) { + Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length)); + } + if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) { + int snetTagId = 0x534e4554; + String bugId = "31850211"; + int uid = -1; + String data = DhcpPacket.ParseException.class.getName(); + EventLog.writeEvent(snetTagId, bugId, uid, data); + } + logError(e.errorCode); + } + } + if (DBG) Log.d(TAG, "Receive thread stopped"); + } + } + + private short getSecs() { + return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000); + } + + private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) { + try { + if (encap == DhcpPacket.ENCAP_L2) { + if (DBG) Log.d(TAG, "Broadcasting " + description); + Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); + } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) { + if (DBG) Log.d(TAG, "Broadcasting " + description); + // We only send L3-encapped broadcasts in DhcpRebindingState, + // where we have an IP address and an unconnected UDP socket. + // + // N.B.: We only need this codepath because DhcpRequestPacket + // hardcodes the source IP address to 0.0.0.0. We could reuse + // the packet socket if this ever changes. + Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER); + } else { + // It's safe to call getpeername here, because we only send unicast packets if we + // have an IP address, and we connect the UDP socket in DhcpBoundState#enter. + if (DBG) Log.d(TAG, String.format("Unicasting %s to %s", + description, Os.getpeername(mUdpSock))); + Os.write(mUdpSock, buf); + } + } catch(ErrnoException|IOException e) { + Log.e(TAG, "Can't send packet: ", e); + return false; + } + return true; + } + + private boolean sendDiscoverPacket() { + ByteBuffer packet = DhcpPacket.buildDiscoverPacket( + DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, + DO_UNICAST, REQUESTED_PARAMS); + return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); + } + + private boolean sendRequestPacket( + Inet4Address clientAddress, Inet4Address requestedAddress, + Inet4Address serverAddress, Inet4Address to) { + // TODO: should we use the transaction ID from the server? + final int encap = INADDR_ANY.equals(clientAddress) + ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP; + + ByteBuffer packet = DhcpPacket.buildRequestPacket( + encap, mTransactionId, getSecs(), clientAddress, + DO_UNICAST, mHwAddr, requestedAddress, + serverAddress, REQUESTED_PARAMS, null); + String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; + String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + + " request=" + requestedAddress.getHostAddress() + + " serverid=" + serverStr; + return transmitPacket(packet, description, encap, to); + } + + private void scheduleLeaseTimers() { + if (mDhcpLeaseExpiry == 0) { + Log.d(TAG, "Infinite lease, no timer scheduling needed"); + return; + } + + final long now = SystemClock.elapsedRealtime(); + + // TODO: consider getting the renew and rebind timers from T1 and T2. + // See also: + // https://tools.ietf.org/html/rfc2131#section-4.4.5 + // https://tools.ietf.org/html/rfc1533#section-9.9 + // https://tools.ietf.org/html/rfc1533#section-9.10 + final long remainingDelay = mDhcpLeaseExpiry - now; + final long renewDelay = remainingDelay / 2; + final long rebindDelay = remainingDelay * 7 / 8; + mRenewAlarm.schedule(now + renewDelay); + mRebindAlarm.schedule(now + rebindDelay); + mExpiryAlarm.schedule(now + remainingDelay); + Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s"); + Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s"); + Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s"); + } + + private void notifySuccess() { + mController.sendMessage( + CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); + } + + private void notifyFailure() { + mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null); + } + + private void acceptDhcpResults(DhcpResults results, String msg) { + mDhcpLease = results; + mOffer = null; + Log.d(TAG, msg + " lease: " + mDhcpLease); + notifySuccess(); + } + + private void clearDhcpState() { + mDhcpLease = null; + mDhcpLeaseExpiry = 0; + mOffer = null; + } + + /** + * Quit the DhcpStateMachine. + * + * @hide + */ + public void doQuit() { + Log.d(TAG, "doQuit"); + quit(); + } + + @Override + protected void onQuitting() { + Log.d(TAG, "onQuitting"); + mController.sendMessage(CMD_ON_QUIT); + } + + abstract class LoggingState extends State { + private long mEnterTimeMs; + + @Override + public void enter() { + if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); + mEnterTimeMs = SystemClock.elapsedRealtime(); + } + + @Override + public void exit() { + long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs; + logState(getName(), (int) durationMs); + } + + private String messageName(int what) { + return sMessageNames.get(what, Integer.toString(what)); + } + + private String messageToString(Message message) { + long now = SystemClock.uptimeMillis(); + return new StringBuilder(" ") + .append(message.getWhen() - now) + .append(messageName(message.what)) + .append(" ").append(message.arg1) + .append(" ").append(message.arg2) + .append(" ").append(message.obj) + .toString(); + } + + @Override + public boolean processMessage(Message message) { + if (MSG_DBG) { + Log.d(TAG, getName() + messageToString(message)); + } + return NOT_HANDLED; + } + + @Override + public String getName() { + // All DhcpClient's states are inner classes with a well defined name. + // Use getSimpleName() and avoid super's getName() creating new String instances. + return getClass().getSimpleName(); + } + } + + // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with + // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. + abstract class WaitBeforeOtherState extends LoggingState { + protected State mOtherState; + + @Override + public void enter() { + super.enter(); + mController.sendMessage(CMD_PRE_DHCP_ACTION); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_PRE_DHCP_ACTION_COMPLETE: + transitionTo(mOtherState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class StoppedState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_START_DHCP: + if (mRegisteredForPreDhcpNotification) { + transitionTo(mWaitBeforeStartState); + } else { + transitionTo(mDhcpInitState); + } + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class WaitBeforeStartState extends WaitBeforeOtherState { + public WaitBeforeStartState(State otherState) { + super(); + mOtherState = otherState; + } + } + + class WaitBeforeRenewalState extends WaitBeforeOtherState { + public WaitBeforeRenewalState(State otherState) { + super(); + mOtherState = otherState; + } + } + + class DhcpState extends State { + @Override + public void enter() { + clearDhcpState(); + if (initInterface() && initSockets()) { + mReceiveThread = new ReceiveThread(); + mReceiveThread.start(); + } else { + notifyFailure(); + transitionTo(mStoppedState); + } + } + + @Override + public void exit() { + if (mReceiveThread != null) { + mReceiveThread.halt(); // Also closes sockets. + mReceiveThread = null; + } + clearDhcpState(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_STOP_DHCP: + transitionTo(mStoppedState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + public boolean isValidPacket(DhcpPacket packet) { + // TODO: check checksum. + int xid = packet.getTransactionId(); + if (xid != mTransactionId) { + Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId); + return false; + } + if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { + Log.d(TAG, "MAC addr mismatch: got " + + HexDump.toHexString(packet.getClientMac()) + ", expected " + + HexDump.toHexString(packet.getClientMac())); + return false; + } + return true; + } + + public void setDhcpLeaseExpiry(DhcpPacket packet) { + long leaseTimeMillis = packet.getLeaseTimeMillis(); + mDhcpLeaseExpiry = + (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0; + } + + /** + * Retransmits packets using jittered exponential backoff with an optional timeout. Packet + * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass + * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout + * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the + * state. + * + * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a + * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET + * sent by the receive thread. They may also set mTimeout and implement timeout. + */ + abstract class PacketRetransmittingState extends LoggingState { + + private int mTimer; + protected int mTimeout = 0; + + @Override + public void enter() { + super.enter(); + initTimer(); + maybeInitTimeout(); + sendMessage(CMD_KICK); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_KICK: + sendPacket(); + scheduleKick(); + return HANDLED; + case CMD_RECEIVED_PACKET: + receivePacket((DhcpPacket) message.obj); + return HANDLED; + case CMD_TIMEOUT: + timeout(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + public void exit() { + super.exit(); + mKickAlarm.cancel(); + mTimeoutAlarm.cancel(); + } + + abstract protected boolean sendPacket(); + abstract protected void receivePacket(DhcpPacket packet); + protected void timeout() {} + + protected void initTimer() { + mTimer = FIRST_TIMEOUT_MS; + } + + protected int jitterTimer(int baseTimer) { + int maxJitter = baseTimer / 10; + int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; + return baseTimer + jitter; + } + + protected void scheduleKick() { + long now = SystemClock.elapsedRealtime(); + long timeout = jitterTimer(mTimer); + long alarmTime = now + timeout; + mKickAlarm.schedule(alarmTime); + mTimer *= 2; + if (mTimer > MAX_TIMEOUT_MS) { + mTimer = MAX_TIMEOUT_MS; + } + } + + protected void maybeInitTimeout() { + if (mTimeout > 0) { + long alarmTime = SystemClock.elapsedRealtime() + mTimeout; + mTimeoutAlarm.schedule(alarmTime); + } + } + } + + class DhcpInitState extends PacketRetransmittingState { + public DhcpInitState() { + super(); + } + + @Override + public void enter() { + super.enter(); + startNewTransaction(); + mLastInitEnterTime = SystemClock.elapsedRealtime(); + } + + protected boolean sendPacket() { + return sendDiscoverPacket(); + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if (!(packet instanceof DhcpOfferPacket)) return; + mOffer = packet.toDhcpResults(); + if (mOffer != null) { + Log.d(TAG, "Got pending lease: " + mOffer); + transitionTo(mDhcpRequestingState); + } + } + } + + // Not implemented. We request the first offer we receive. + class DhcpSelectingState extends LoggingState { + } + + class DhcpRequestingState extends PacketRetransmittingState { + public DhcpRequestingState() { + mTimeout = DHCP_TIMEOUT_MS / 2; + } + + protected boolean sendPacket() { + return sendRequestPacket( + INADDR_ANY, // ciaddr + (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP + (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER + INADDR_BROADCAST); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + DhcpResults results = packet.toDhcpResults(); + if (results != null) { + setDhcpLeaseExpiry(packet); + acceptDhcpResults(results, "Confirmed"); + transitionTo(mConfiguringInterfaceState); + } + } else if (packet instanceof DhcpNakPacket) { + // TODO: Wait a while before returning into INIT state. + Log.d(TAG, "Received NAK, returning to INIT"); + mOffer = null; + transitionTo(mDhcpInitState); + } + } + + @Override + protected void timeout() { + // After sending REQUESTs unsuccessfully for a while, go back to init. + transitionTo(mDhcpInitState); + } + } + + class DhcpHaveLeaseState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_EXPIRE_DHCP: + Log.d(TAG, "Lease expired!"); + notifyFailure(); + transitionTo(mDhcpInitState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + public void exit() { + // Clear any extant alarms. + mRenewAlarm.cancel(); + mRebindAlarm.cancel(); + mExpiryAlarm.cancel(); + clearDhcpState(); + // Tell IpManager to clear the IPv4 address. There is no need to + // wait for confirmation since any subsequent packets are sent from + // INADDR_ANY anyway (DISCOVER, REQUEST). + mController.sendMessage(CMD_CLEAR_LINKADDRESS); + } + } + + class ConfiguringInterfaceState extends LoggingState { + @Override + public void enter() { + super.enter(); + mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case EVENT_LINKADDRESS_CONFIGURED: + transitionTo(mDhcpBoundState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class DhcpBoundState extends LoggingState { + @Override + public void enter() { + super.enter(); + if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) { + // There's likely no point in going into DhcpInitState here, we'll probably + // just repeat the transaction, get the same IP address as before, and fail. + // + // NOTE: It is observed that connectUdpSock() basically never fails, due to + // SO_BINDTODEVICE. Examining the local socket address shows it will happily + // return an IPv4 address from another interface, or even return "0.0.0.0". + // + // TODO: Consider deleting this check, following testing on several kernels. + notifyFailure(); + transitionTo(mStoppedState); + } + + scheduleLeaseTimers(); + logTimeToBoundState(); + } + + @Override + public void exit() { + super.exit(); + mLastBoundExitTime = SystemClock.elapsedRealtime(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_RENEW_DHCP: + if (mRegisteredForPreDhcpNotification) { + transitionTo(mWaitBeforeRenewalState); + } else { + transitionTo(mDhcpRenewingState); + } + return HANDLED; + default: + return NOT_HANDLED; + } + } + + private void logTimeToBoundState() { + long now = SystemClock.elapsedRealtime(); + if (mLastBoundExitTime > mLastInitEnterTime) { + logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime)); + } else { + logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime)); + } + } + } + + abstract class DhcpReacquiringState extends PacketRetransmittingState { + protected String mLeaseMsg; + + @Override + public void enter() { + super.enter(); + startNewTransaction(); + } + + abstract protected Inet4Address packetDestination(); + + protected boolean sendPacket() { + return sendRequestPacket( + (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr + INADDR_ANY, // DHCP_REQUESTED_IP + null, // DHCP_SERVER_IDENTIFIER + packetDestination()); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + final DhcpResults results = packet.toDhcpResults(); + if (results != null) { + if (!mDhcpLease.ipAddress.equals(results.ipAddress)) { + Log.d(TAG, "Renewed lease not for our current IP address!"); + notifyFailure(); + transitionTo(mDhcpInitState); + } + setDhcpLeaseExpiry(packet); + // Updating our notion of DhcpResults here only causes the + // DNS servers and routes to be updated in LinkProperties + // in IpManager and by any overridden relevant handlers of + // the registered IpManager.Callback. IP address changes + // are not supported here. + acceptDhcpResults(results, mLeaseMsg); + transitionTo(mDhcpBoundState); + } + } else if (packet instanceof DhcpNakPacket) { + Log.d(TAG, "Received NAK, returning to INIT"); + notifyFailure(); + transitionTo(mDhcpInitState); + } + } + } + + class DhcpRenewingState extends DhcpReacquiringState { + public DhcpRenewingState() { + mLeaseMsg = "Renewed"; + } + + @Override + public boolean processMessage(Message message) { + if (super.processMessage(message) == HANDLED) { + return HANDLED; + } + + switch (message.what) { + case CMD_REBIND_DHCP: + transitionTo(mDhcpRebindingState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + protected Inet4Address packetDestination() { + // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but... + // http://b/25343517 . Try to make things work anyway by using broadcast renews. + return (mDhcpLease.serverAddress != null) ? + mDhcpLease.serverAddress : INADDR_BROADCAST; + } + } + + class DhcpRebindingState extends DhcpReacquiringState { + public DhcpRebindingState() { + mLeaseMsg = "Rebound"; + } + + @Override + public void enter() { + super.enter(); + + // We need to broadcast and possibly reconnect the socket to a + // completely different server. + closeQuietly(mUdpSock); + if (!initUdpSocket()) { + Log.e(TAG, "Failed to recreate UDP socket"); + transitionTo(mDhcpInitState); + } + } + + @Override + protected Inet4Address packetDestination() { + return INADDR_BROADCAST; + } + } + + class DhcpInitRebootState extends LoggingState { + } + + class DhcpRebootingState extends LoggingState { + } + + private void logError(int errorCode) { + mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode)); + } + + private void logState(String name, int durationMs) { + final DhcpClientEvent event = new DhcpClientEvent.Builder() + .setMsg(name) + .setDurationMs(durationMs) + .build(); + mMetricsLog.log(mIfaceName, event); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java index 7ecdea7b89da..7ecdea7b89da 100644 --- a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java index 11f2b6118e24..11f2b6118e24 100644 --- a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java index 7a83466c6e05..7a83466c6e05 100644 --- a/services/net/java/android/net/dhcp/DhcpInformPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java index 1da0b7300559..1da0b7300559 100644 --- a/services/net/java/android/net/dhcp/DhcpNakPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java index 0eba77e4a682..0eba77e4a682 100644 --- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java index ce8b7e78d0f8..ce8b7e78d0f8 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java index 39583032c20d..39583032c20d 100644 --- a/services/net/java/android/net/dhcp/DhcpReleasePacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java index 231d04576c28..231d04576c28 100644 --- a/services/net/java/android/net/dhcp/DhcpRequestPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java index 385dd52e4576..385dd52e4576 100644 --- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java +++ b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java new file mode 100644 index 000000000000..ad7f85d0a30a --- /dev/null +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -0,0 +1,1691 @@ +/* + * Copyright (C) 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.ip; + +import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; + +import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.DhcpResults; +import android.net.INetd; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.ProvisioningConfigurationParcelable; +import android.net.ProxyInfo; +import android.net.ProxyInfoParcelable; +import android.net.RouteInfo; +import android.net.apf.ApfCapabilities; +import android.net.apf.ApfFilter; +import android.net.dhcp.DhcpClient; +import android.net.ip.IIpClientCallbacks; +import android.net.metrics.IpConnectivityLog; +import android.net.metrics.IpManagerEvent; +import android.net.shared.InitialConfiguration; +import android.net.shared.NetdService; +import android.net.shared.ProvisioningConfiguration; +import android.net.util.InterfaceParams; +import android.net.util.SharedLog; +import android.os.ConditionVariable; +import android.os.INetworkManagementService; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.LocalLog; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IState; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.Preconditions; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; +import com.android.server.net.NetlinkTracker; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.function.Predicate; +import java.util.stream.Collectors; + + +/** + * IpClient + * + * This class provides the interface to IP-layer provisioning and maintenance + * functionality that can be used by transport layers like Wi-Fi, Ethernet, + * et cetera. + * + * [ Lifetime ] + * IpClient is designed to be instantiated as soon as the interface name is + * known and can be as long-lived as the class containing it (i.e. declaring + * it "private final" is okay). + * + * @hide + */ +public class IpClient extends StateMachine { + private static final boolean DBG = false; + + // For message logging. + private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class }; + private static final SparseArray<String> sWhatToString = + MessageUtils.findMessageNames(sMessageClasses); + // Two static concurrent hashmaps of interface name to logging classes. + // One holds StateMachine logs and the other connectivity packet logs. + private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>(); + + /** + * Dump all state machine and connectivity packet logs to the specified writer. + * @param skippedIfaces Interfaces for which logs should not be dumped. + */ + public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) { + for (String ifname : sSmLogs.keySet()) { + if (skippedIfaces.contains(ifname)) continue; + + writer.println(String.format("--- BEGIN %s ---", ifname)); + + final SharedLog smLog = sSmLogs.get(ifname); + if (smLog != null) { + writer.println("State machine log:"); + smLog.dump(null, writer, null); + } + + writer.println(""); + + final LocalLog pktLog = sPktLogs.get(ifname); + if (pktLog != null) { + writer.println("Connectivity packet log:"); + pktLog.readOnlyLocalLog().dump(null, writer, null); + } + + writer.println(String.format("--- END %s ---", ifname)); + } + } + + // Use a wrapper class to log in order to ensure complete and detailed + // logging. This method is lighter weight than annotations/reflection + // and has the following benefits: + // + // - No invoked method can be forgotten. + // Any new method added to IpClient.Callback must be overridden + // here or it will never be called. + // + // - No invoking call site can be forgotten. + // Centralized logging in this way means call sites don't need to + // remember to log, and therefore no call site can be forgotten. + // + // - No variation in log format among call sites. + // Encourages logging of any available arguments, and all call sites + // are necessarily logged identically. + // + // NOTE: Log first because passed objects may or may not be thread-safe and + // once passed on to the callback they may be modified by another thread. + // + // TODO: Find an lighter weight approach. + public static class IpClientCallbacksWrapper { + private static final String PREFIX = "INVOKE "; + private final IIpClientCallbacks mCallback; + private final SharedLog mLog; + + @VisibleForTesting + protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) { + mCallback = callback; + mLog = log; + } + + private void log(String msg) { + mLog.log(PREFIX + msg); + } + + private void log(String msg, Throwable e) { + mLog.e(PREFIX + msg, e); + } + + public void onPreDhcpAction() { + log("onPreDhcpAction()"); + try { + mCallback.onPreDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPreDhcpAction", e); + } + } + + public void onPostDhcpAction() { + log("onPostDhcpAction()"); + try { + mCallback.onPostDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPostDhcpAction", e); + } + } + + public void onNewDhcpResults(DhcpResults dhcpResults) { + log("onNewDhcpResults({" + dhcpResults + "})"); + try { + mCallback.onNewDhcpResults(toStableParcelable(dhcpResults)); + } catch (RemoteException e) { + log("Failed to call onNewDhcpResults", e); + } + } + + public void onProvisioningSuccess(LinkProperties newLp) { + log("onProvisioningSuccess({" + newLp + "})"); + try { + mCallback.onProvisioningSuccess(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningSuccess", e); + } + } + + public void onProvisioningFailure(LinkProperties newLp) { + log("onProvisioningFailure({" + newLp + "})"); + try { + mCallback.onProvisioningFailure(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningFailure", e); + } + } + + public void onLinkPropertiesChange(LinkProperties newLp) { + log("onLinkPropertiesChange({" + newLp + "})"); + try { + mCallback.onLinkPropertiesChange(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onLinkPropertiesChange", e); + } + } + + public void onReachabilityLost(String logMsg) { + log("onReachabilityLost(" + logMsg + ")"); + try { + mCallback.onReachabilityLost(logMsg); + } catch (RemoteException e) { + log("Failed to call onReachabilityLost", e); + } + } + + public void onQuit() { + log("onQuit()"); + try { + mCallback.onQuit(); + } catch (RemoteException e) { + log("Failed to call onQuit", e); + } + } + + public void installPacketFilter(byte[] filter) { + log("installPacketFilter(byte[" + filter.length + "])"); + try { + mCallback.installPacketFilter(filter); + } catch (RemoteException e) { + log("Failed to call installPacketFilter", e); + } + } + + public void startReadPacketFilter() { + log("startReadPacketFilter()"); + try { + mCallback.startReadPacketFilter(); + } catch (RemoteException e) { + log("Failed to call startReadPacketFilter", e); + } + } + + public void setFallbackMulticastFilter(boolean enabled) { + log("setFallbackMulticastFilter(" + enabled + ")"); + try { + mCallback.setFallbackMulticastFilter(enabled); + } catch (RemoteException e) { + log("Failed to call setFallbackMulticastFilter", e); + } + } + + public void setNeighborDiscoveryOffload(boolean enable) { + log("setNeighborDiscoveryOffload(" + enable + ")"); + try { + mCallback.setNeighborDiscoveryOffload(enable); + } catch (RemoteException e) { + log("Failed to call setNeighborDiscoveryOffload", e); + } + } + } + + public static final String DUMP_ARG_CONFIRM = "confirm"; + + private static final int CMD_TERMINATE_AFTER_STOP = 1; + private static final int CMD_STOP = 2; + private static final int CMD_START = 3; + private static final int CMD_CONFIRM = 4; + private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; + // Triggered by NetlinkTracker to communicate netlink events. + private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; + private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; + private static final int CMD_UPDATE_HTTP_PROXY = 8; + private static final int CMD_SET_MULTICAST_FILTER = 9; + private static final int EVENT_PROVISIONING_TIMEOUT = 10; + private static final int EVENT_DHCPACTION_TIMEOUT = 11; + private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; + + // Internal commands to use instead of trying to call transitionTo() inside + // a given State's enter() method. Calling transitionTo() from enter/exit + // encounters a Log.wtf() that can cause trouble on eng builds. + private static final int CMD_JUMP_STARTED_TO_RUNNING = 100; + private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101; + private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102; + + // IpClient shares a handler with DhcpClient: commands must not overlap + public static final int DHCPCLIENT_CMD_BASE = 1000; + + private static final int MAX_LOG_RECORDS = 500; + private static final int MAX_PACKET_RECORDS = 100; + + private static final boolean NO_CALLBACKS = false; + private static final boolean SEND_CALLBACKS = true; + + // This must match the interface prefix in clatd.c. + // TODO: Revert this hack once IpClient and Nat464Xlat work in concert. + private static final String CLAT_PREFIX = "v4-"; + + private static final int IMMEDIATE_FAILURE_DURATION = 0; + + private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1; + private static final int PROV_CHANGE_LOST_PROVISIONING = 2; + private static final int PROV_CHANGE_GAINED_PROVISIONING = 3; + private static final int PROV_CHANGE_STILL_PROVISIONED = 4; + + private final State mStoppedState = new StoppedState(); + private final State mStoppingState = new StoppingState(); + private final State mStartedState = new StartedState(); + private final State mRunningState = new RunningState(); + + private final String mTag; + private final Context mContext; + private final String mInterfaceName; + private final String mClatInterfaceName; + @VisibleForTesting + protected final IpClientCallbacksWrapper mCallback; + private final Dependencies mDependencies; + private final CountDownLatch mShutdownLatch; + private final ConnectivityManager mCm; + private final INetworkManagementService mNwService; + private final NetlinkTracker mNetlinkTracker; + private final WakeupMessage mProvisioningTimeoutAlarm; + private final WakeupMessage mDhcpActionTimeoutAlarm; + private final SharedLog mLog; + private final LocalLog mConnectivityPacketLog; + private final MessageHandlingLogger mMsgStateLogger; + private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); + private final InterfaceController mInterfaceCtrl; + + private InterfaceParams mInterfaceParams; + + /** + * Non-final member variables accessed only from within our StateMachine. + */ + private LinkProperties mLinkProperties; + private android.net.shared.ProvisioningConfiguration mConfiguration; + private IpReachabilityMonitor mIpReachabilityMonitor; + private DhcpClient mDhcpClient; + private DhcpResults mDhcpResults; + private String mTcpBufferSizes; + private ProxyInfo mHttpProxy; + private ApfFilter mApfFilter; + private boolean mMulticastFiltering; + private long mStartTimeMillis; + + /** + * Reading the snapshot is an asynchronous operation initiated by invoking + * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an + * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable + * signals when a new snapshot is ready. + */ + private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable(); + + public static class Dependencies { + public INetworkManagementService getNMS() { + return INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + } + + public INetd getNetd() { + return NetdService.getInstance(); + } + + /** + * Get interface parameters for the specified interface. + */ + public InterfaceParams getInterfaceParams(String ifname) { + return InterfaceParams.getByName(ifname); + } + } + + public IpClient(Context context, String ifName, IIpClientCallbacks callback) { + this(context, ifName, callback, new Dependencies()); + } + + /** + * An expanded constructor, useful for dependency injection. + * TODO: migrate all test users to mock IpClient directly and remove this ctor. + */ + public IpClient(Context context, String ifName, IIpClientCallbacks callback, + INetworkManagementService nwService) { + this(context, ifName, callback, new Dependencies() { + @Override + public INetworkManagementService getNMS() { + return nwService; + } + }); + } + + @VisibleForTesting + IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) { + super(IpClient.class.getSimpleName() + "." + ifName); + Preconditions.checkNotNull(ifName); + Preconditions.checkNotNull(callback); + + mTag = getName(); + + mContext = context; + mInterfaceName = ifName; + mClatInterfaceName = CLAT_PREFIX + ifName; + mDependencies = deps; + mShutdownLatch = new CountDownLatch(1); + mCm = mContext.getSystemService(ConnectivityManager.class); + mNwService = deps.getNMS(); + + sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag)); + mLog = sSmLogs.get(mInterfaceName); + sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS)); + mConnectivityPacketLog = sPktLogs.get(mInterfaceName); + mMsgStateLogger = new MessageHandlingLogger(); + mCallback = new IpClientCallbacksWrapper(callback, mLog); + + // TODO: Consider creating, constructing, and passing in some kind of + // InterfaceController.Dependencies class. + mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog); + + mNetlinkTracker = new NetlinkTracker( + mInterfaceName, + new NetlinkTracker.Callback() { + @Override + public void update() { + sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); + } + }) { + @Override + public void interfaceAdded(String iface) { + super.interfaceAdded(iface); + if (mClatInterfaceName.equals(iface)) { + mCallback.setNeighborDiscoveryOffload(false); + } else if (!mInterfaceName.equals(iface)) { + return; + } + + final String msg = "interfaceAdded(" + iface + ")"; + logMsg(msg); + } + + @Override + public void interfaceRemoved(String iface) { + super.interfaceRemoved(iface); + // TODO: Also observe mInterfaceName going down and take some + // kind of appropriate action. + if (mClatInterfaceName.equals(iface)) { + // TODO: consider sending a message to the IpClient main + // StateMachine thread, in case "NDO enabled" state becomes + // tied to more things that 464xlat operation. + mCallback.setNeighborDiscoveryOffload(true); + } else if (!mInterfaceName.equals(iface)) { + return; + } + + final String msg = "interfaceRemoved(" + iface + ")"; + logMsg(msg); + } + + private void logMsg(String msg) { + Log.d(mTag, msg); + getHandler().post(() -> mLog.log("OBSERVED " + msg)); + } + }; + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + + mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); + mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); + + // Anything the StateMachine may access must have been instantiated + // before this point. + configureAndStartStateMachine(); + + // Anything that may send messages to the StateMachine must only be + // configured to do so after the StateMachine has started (above). + startStateMachineUpdaters(); + } + + /** + * Make a IIpClient connector to communicate with this IpClient. + */ + public IIpClient makeConnector() { + return new IpClientConnector(); + } + + class IpClientConnector extends IIpClient.Stub { + @Override + public void completedPreDhcpAction() { + checkNetworkStackCallingPermission(); + IpClient.this.completedPreDhcpAction(); + } + @Override + public void confirmConfiguration() { + checkNetworkStackCallingPermission(); + IpClient.this.confirmConfiguration(); + } + @Override + public void readPacketFilterComplete(byte[] data) { + checkNetworkStackCallingPermission(); + IpClient.this.readPacketFilterComplete(data); + } + @Override + public void shutdown() { + checkNetworkStackCallingPermission(); + IpClient.this.shutdown(); + } + @Override + public void startProvisioning(ProvisioningConfigurationParcelable req) { + checkNetworkStackCallingPermission(); + IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req)); + } + @Override + public void stop() { + checkNetworkStackCallingPermission(); + IpClient.this.stop(); + } + @Override + public void setTcpBufferSizes(String tcpBufferSizes) { + checkNetworkStackCallingPermission(); + IpClient.this.setTcpBufferSizes(tcpBufferSizes); + } + @Override + public void setHttpProxy(ProxyInfoParcelable proxyInfo) { + checkNetworkStackCallingPermission(); + IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo)); + } + @Override + public void setMulticastFilter(boolean enabled) { + checkNetworkStackCallingPermission(); + IpClient.this.setMulticastFilter(enabled); + } + } + + public String getInterfaceName() { + return mInterfaceName; + } + + private void configureAndStartStateMachine() { + // CHECKSTYLE:OFF IndentationCheck + addState(mStoppedState); + addState(mStartedState); + addState(mRunningState, mStartedState); + addState(mStoppingState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mStoppedState); + + super.start(); + } + + private void startStateMachineUpdaters() { + try { + mNwService.registerObserver(mNetlinkTracker); + } catch (RemoteException e) { + logError("Couldn't register NetlinkTracker: %s", e); + } + } + + private void stopStateMachineUpdaters() { + try { + mNwService.unregisterObserver(mNetlinkTracker); + } catch (RemoteException e) { + logError("Couldn't unregister NetlinkTracker: %s", e); + } + } + + @Override + protected void onQuitting() { + mCallback.onQuit(); + mShutdownLatch.countDown(); + } + + /** + * Shut down this IpClient instance altogether. + */ + public void shutdown() { + stop(); + sendMessage(CMD_TERMINATE_AFTER_STOP); + } + + /** + * Start provisioning with the provided parameters. + */ + public void startProvisioning(ProvisioningConfiguration req) { + if (!req.isValid()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + return; + } + + mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName); + if (mInterfaceParams == null) { + logError("Failed to find InterfaceParams for " + mInterfaceName); + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND); + return; + } + + mCallback.setNeighborDiscoveryOffload(true); + sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); + } + + /** + * Stop this IpClient. + * + * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}. + */ + public void stop() { + sendMessage(CMD_STOP); + } + + /** + * Confirm the provisioning configuration. + */ + public void confirmConfiguration() { + sendMessage(CMD_CONFIRM); + } + + /** + * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be + * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to + * proceed. + */ + public void completedPreDhcpAction() { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + + /** + * Indicate that packet filter read is complete. + */ + public void readPacketFilterComplete(byte[] data) { + sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data); + } + + /** + * Set the TCP buffer sizes to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setTcpBufferSizes(String tcpBufferSizes) { + sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); + } + + /** + * Set the HTTP Proxy configuration to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setHttpProxy(ProxyInfo proxyInfo) { + sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); + } + + /** + * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, + * if not, Callback.setFallbackMulticastFilter() is called. + */ + public void setMulticastFilter(boolean enabled) { + sendMessage(CMD_SET_MULTICAST_FILTER, enabled); + } + + /** + * Dump logs of this IpClient. + */ + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { + // Execute confirmConfiguration() and take no further action. + confirmConfiguration(); + return; + } + + // Thread-unsafe access to mApfFilter but just used for debugging. + final ApfFilter apfFilter = mApfFilter; + final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration; + final ApfCapabilities apfCapabilities = (provisioningConfig != null) + ? provisioningConfig.mApfCapabilities : null; + + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println(mTag + " APF dump:"); + pw.increaseIndent(); + if (apfFilter != null) { + if (apfCapabilities.hasDataAccess()) { + // Request a new snapshot, then wait for it. + mApfDataSnapshotComplete.close(); + mCallback.startReadPacketFilter(); + if (!mApfDataSnapshotComplete.block(1000)) { + pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT"); + } + } + apfFilter.dump(pw); + + } else { + pw.print("No active ApfFilter; "); + if (provisioningConfig == null) { + pw.println("IpClient not yet started."); + } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { + pw.println("Hardware does not support APF."); + } else { + pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); + } + } + pw.decreaseIndent(); + pw.println(); + pw.println(mTag + " current ProvisioningConfiguration:"); + pw.increaseIndent(); + pw.println(Objects.toString(provisioningConfig, "N/A")); + pw.decreaseIndent(); + + final IpReachabilityMonitor iprm = mIpReachabilityMonitor; + if (iprm != null) { + pw.println(); + pw.println(mTag + " current IpReachabilityMonitor state:"); + pw.increaseIndent(); + iprm.dump(pw); + pw.decreaseIndent(); + } + + pw.println(); + pw.println(mTag + " StateMachine dump:"); + pw.increaseIndent(); + mLog.dump(fd, pw, args); + pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " connectivity packet log:"); + pw.println(); + pw.println("Debug with python and scapy via:"); + pw.println("shell$ python"); + pw.println(">>> from scapy import all as scapy"); + pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); + pw.println(); + + pw.increaseIndent(); + mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); + pw.decreaseIndent(); + } + + + /** + * Internals. + */ + + @Override + protected String getWhatToString(int what) { + return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); + } + + @Override + protected String getLogRecString(Message msg) { + final String logLine = String.format( + "%s/%d %d %d %s [%s]", + mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index, + msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); + + final String richerLogLine = getWhatToString(msg.what) + " " + logLine; + mLog.log(richerLogLine); + if (DBG) { + Log.d(mTag, richerLogLine); + } + + mMsgStateLogger.reset(); + return logLine; + } + + @Override + protected boolean recordLogRec(Message msg) { + // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, + // and we already log any LinkProperties change that results in an + // invocation of IpClient.Callback#onLinkPropertiesChange(). + final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); + if (!shouldLog) { + mMsgStateLogger.reset(); + } + return shouldLog; + } + + private void logError(String fmt, Object... args) { + final String msg = "ERROR " + String.format(fmt, args); + Log.e(mTag, msg); + mLog.log(msg); + } + + // This needs to be called with care to ensure that our LinkProperties + // are in sync with the actual LinkProperties of the interface. For example, + // we should only call this if we know for sure that there are no IP addresses + // assigned to the interface, etc. + private void resetLinkProperties() { + mNetlinkTracker.clearLinkProperties(); + mConfiguration = null; + mDhcpResults = null; + mTcpBufferSizes = ""; + mHttpProxy = null; + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + } + + private void recordMetric(final int type) { + // We may record error metrics prior to starting. + // Map this to IMMEDIATE_FAILURE_DURATION. + final long duration = (mStartTimeMillis > 0) + ? (SystemClock.elapsedRealtime() - mStartTimeMillis) + : IMMEDIATE_FAILURE_DURATION; + mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); + } + + // For now: use WifiStateMachine's historical notion of provisioned. + @VisibleForTesting + static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) { + // For historical reasons, we should connect even if all we have is + // an IPv4 address and nothing else. + if (lp.hasIPv4Address() || lp.isProvisioned()) { + return true; + } + if (config == null) { + return false; + } + + // When an InitialConfiguration is specified, ignore any difference with previous + // properties and instead check if properties observed match the desired properties. + return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); + } + + // TODO: Investigate folding all this into the existing static function + // LinkProperties.compareProvisioning() or some other single function that + // takes two LinkProperties objects and returns a ProvisioningChange + // object that is a correct and complete assessment of what changed, taking + // account of the asymmetries described in the comments in this function. + // Then switch to using it everywhere (IpReachabilityMonitor, etc.). + private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { + int delta; + InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; + final boolean wasProvisioned = isProvisioned(oldLp, config); + final boolean isProvisioned = isProvisioned(newLp, config); + + if (!wasProvisioned && isProvisioned) { + delta = PROV_CHANGE_GAINED_PROVISIONING; + } else if (wasProvisioned && isProvisioned) { + delta = PROV_CHANGE_STILL_PROVISIONED; + } else if (!wasProvisioned && !isProvisioned) { + delta = PROV_CHANGE_STILL_NOT_PROVISIONED; + } else { + // (wasProvisioned && !isProvisioned) + // + // Note that this is true even if we lose a configuration element + // (e.g., a default gateway) that would not be required to advance + // into provisioned state. This is intended: if we have a default + // router and we lose it, that's a sure sign of a problem, but if + // we connect to a network with no IPv4 DNS servers, we consider + // that to be a network without DNS servers and connect anyway. + // + // See the comment below. + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); + final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address(); + final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute(); + + // If bad wifi avoidance is disabled, then ignore IPv6 loss of + // provisioning. Otherwise, when a hotspot that loses Internet + // access sends out a 0-lifetime RA to its clients, the clients + // will disconnect and then reconnect, avoiding the bad hotspot, + // instead of getting stuck on the bad hotspot. http://b/31827713 . + // + // This is incorrect because if the hotspot then regains Internet + // access with a different prefix, TCP connections on the + // deprecated addresses will remain stuck. + // + // Note that we can still be disconnected by IpReachabilityMonitor + // if the IPv6 default gateway (but not the IPv6 DNS servers; see + // accompanying code in IpReachabilityMonitor) is unreachable. + final boolean ignoreIPv6ProvisioningLoss = + mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker + && mCm.getAvoidBadWifi(); + + // Additionally: + // + // Partial configurations (e.g., only an IPv4 address with no DNS + // servers and no default route) are accepted as long as DHCPv4 + // succeeds. On such a network, isProvisioned() will always return + // false, because the configuration is not complete, but we want to + // connect anyway. It might be a disconnected network such as a + // Chromecast or a wireless printer, for example. + // + // Because on such a network isProvisioned() will always return false, + // delta will never be LOST_PROVISIONING. So check for loss of + // provisioning here too. + if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + // Additionally: + // + // If the previous link properties had a global IPv6 address and an + // IPv6 default route then also consider the loss of that default route + // to be a loss of provisioning. See b/27962810. + if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + return delta; + } + + private void dispatchCallback(int delta, LinkProperties newLp) { + switch (delta) { + case PROV_CHANGE_GAINED_PROVISIONING: + if (DBG) { + Log.d(mTag, "onProvisioningSuccess()"); + } + recordMetric(IpManagerEvent.PROVISIONING_OK); + mCallback.onProvisioningSuccess(newLp); + break; + + case PROV_CHANGE_LOST_PROVISIONING: + if (DBG) { + Log.d(mTag, "onProvisioningFailure()"); + } + recordMetric(IpManagerEvent.PROVISIONING_FAIL); + mCallback.onProvisioningFailure(newLp); + break; + + default: + if (DBG) { + Log.d(mTag, "onLinkPropertiesChange()"); + } + mCallback.onLinkPropertiesChange(newLp); + break; + } + } + + // Updates all IpClient-related state concerned with LinkProperties. + // Returns a ProvisioningChange for possibly notifying other interested + // parties that are not fronted by IpClient. + private int setLinkProperties(LinkProperties newLp) { + if (mApfFilter != null) { + mApfFilter.setLinkProperties(newLp); + } + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.updateLinkProperties(newLp); + } + + int delta = compareProvisioning(mLinkProperties, newLp); + mLinkProperties = new LinkProperties(newLp); + + if (delta == PROV_CHANGE_GAINED_PROVISIONING) { + // TODO: Add a proper ProvisionedState and cancel the alarm in + // its enter() method. + mProvisioningTimeoutAlarm.cancel(); + } + + return delta; + } + + private LinkProperties assembleLinkProperties() { + // [1] Create a new LinkProperties object to populate. + LinkProperties newLp = new LinkProperties(); + newLp.setInterfaceName(mInterfaceName); + + // [2] Pull in data from netlink: + // - IPv4 addresses + // - IPv6 addresses + // - IPv6 routes + // - IPv6 DNS servers + // + // N.B.: this is fundamentally race-prone and should be fixed by + // changing NetlinkTracker from a hybrid edge/level model to an + // edge-only model, or by giving IpClient its own netlink socket(s) + // so as to track all required information directly. + LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); + newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); + for (RouteInfo route : netlinkLinkProperties.getRoutes()) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); + + // [3] Add in data from DHCPv4, if available. + // + // mDhcpResults is never shared with any other owner so we don't have + // to worry about concurrent modification. + if (mDhcpResults != null) { + for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); + newLp.setDomains(mDhcpResults.domains); + + if (mDhcpResults.mtu != 0) { + newLp.setMtu(mDhcpResults.mtu); + } + } + + // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. + if (!TextUtils.isEmpty(mTcpBufferSizes)) { + newLp.setTcpBufferSizes(mTcpBufferSizes); + } + if (mHttpProxy != null) { + newLp.setHttpProxy(mHttpProxy); + } + + // [5] Add data from InitialConfiguration + if (mConfiguration != null && mConfiguration.mInitialConfig != null) { + InitialConfiguration config = mConfiguration.mInitialConfig; + // Add InitialConfiguration routes and dns server addresses once all addresses + // specified in the InitialConfiguration have been observed with Netlink. + if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { + for (IpPrefix prefix : config.directlyConnectedRoutes) { + newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); + } + } + addAllReachableDnsServers(newLp, config.dnsServers); + } + final LinkProperties oldLp = mLinkProperties; + if (DBG) { + Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", + netlinkLinkProperties, newLp, oldLp)); + } + + // TODO: also learn via netlink routes specified by an InitialConfiguration and specified + // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. + return newLp; + } + + private static void addAllReachableDnsServers( + LinkProperties lp, Iterable<InetAddress> dnses) { + // TODO: Investigate deleting this reachability check. We should be + // able to pass everything down to netd and let netd do evaluation + // and RFC6724-style sorting. + for (InetAddress dns : dnses) { + if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { + lp.addDnsServer(dns); + } + } + } + + // Returns false if we have lost provisioning, true otherwise. + private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { + final LinkProperties newLp = assembleLinkProperties(); + if (Objects.equals(newLp, mLinkProperties)) { + return true; + } + final int delta = setLinkProperties(newLp); + if (sendCallbacks) { + dispatchCallback(delta, newLp); + } + return (delta != PROV_CHANGE_LOST_PROVISIONING); + } + + private void handleIPv4Success(DhcpResults dhcpResults) { + mDhcpResults = new DhcpResults(dhcpResults); + final LinkProperties newLp = assembleLinkProperties(); + final int delta = setLinkProperties(newLp); + + if (DBG) { + Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); + } + mCallback.onNewDhcpResults(dhcpResults); + dispatchCallback(delta, newLp); + } + + private void handleIPv4Failure() { + // TODO: Investigate deleting this clearIPv4Address() call. + // + // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances + // that could trigger a call to this function. If we missed handling + // that message in StartedState for some reason we would still clear + // any addresses upon entry to StoppedState. + mInterfaceCtrl.clearIPv4Address(); + mDhcpResults = null; + if (DBG) { + Log.d(mTag, "onNewDhcpResults(null)"); + } + mCallback.onNewDhcpResults(null); + + handleProvisioningFailure(); + } + + private void handleProvisioningFailure() { + final LinkProperties newLp = assembleLinkProperties(); + int delta = setLinkProperties(newLp); + // If we've gotten here and we're still not provisioned treat that as + // a total loss of provisioning. + // + // Either (a) static IP configuration failed or (b) DHCPv4 failed AND + // there was no usable IPv6 obtained before a non-zero provisioning + // timeout expired. + // + // Regardless: GAME OVER. + if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + dispatchCallback(delta, newLp); + if (delta == PROV_CHANGE_LOST_PROVISIONING) { + transitionTo(mStoppingState); + } + } + + private void doImmediateProvisioningFailure(int failureType) { + logError("onProvisioningFailure(): %s", failureType); + recordMetric(failureType); + mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); + } + + private boolean startIPv4() { + // If we have a StaticIpConfiguration attempt to apply it and + // handle the result accordingly. + if (mConfiguration.mStaticIpConfig != null) { + if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { + handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); + } else { + return false; + } + } else { + // Start DHCPv4. + mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams); + mDhcpClient.registerForPreDhcpNotification(); + mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); + } + + return true; + } + + private boolean startIPv6() { + return mInterfaceCtrl.setIPv6PrivacyExtensions(true) + && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) + && mInterfaceCtrl.enableIPv6(); + } + + private boolean applyInitialConfig(InitialConfiguration config) { + // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. + for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { + if (!mInterfaceCtrl.addAddress(addr)) return false; + } + + return true; + } + + private boolean startIpReachabilityMonitor() { + try { + // TODO: Fetch these parameters from settings, and install a + // settings observer to watch for update and re-program these + // parameters (Q: is this level of dynamic updatability really + // necessary or does reading from settings at startup suffice?). + final int numSolicits = 5; + final int interSolicitIntervalMs = 750; + setNeighborParameters(mDependencies.getNetd(), mInterfaceName, + numSolicits, interSolicitIntervalMs); + } catch (Exception e) { + mLog.e("Failed to adjust neighbor parameters", e); + // Carry on using the system defaults (currently: 3, 1000); + } + + try { + mIpReachabilityMonitor = new IpReachabilityMonitor( + mContext, + mInterfaceParams, + getHandler(), + mLog, + new IpReachabilityMonitor.Callback() { + @Override + public void notifyLost(InetAddress ip, String logMsg) { + mCallback.onReachabilityLost(logMsg); + } + }, + mConfiguration.mUsingMultinetworkPolicyTracker); + } catch (IllegalArgumentException iae) { + // Failed to start IpReachabilityMonitor. Log it and call + // onProvisioningFailure() immediately. + // + // See http://b/31038971. + logError("IpReachabilityMonitor failure: %s", iae); + mIpReachabilityMonitor = null; + } + + return (mIpReachabilityMonitor != null); + } + + private void stopAllIP() { + // We don't need to worry about routes, just addresses, because: + // - disableIpv6() will clear autoconf IPv6 routes as well, and + // - we don't get IPv4 routes from netlink + // so we neither react to nor need to wait for changes in either. + + mInterfaceCtrl.disableIPv6(); + mInterfaceCtrl.clearAllAddresses(); + } + + class StoppedState extends State { + @Override + public void enter() { + stopAllIP(); + + resetLinkProperties(); + if (mStartTimeMillis > 0) { + // Completed a life-cycle; send a final empty LinkProperties + // (cleared in resetLinkProperties() above) and record an event. + mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties)); + recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); + mStartTimeMillis = 0; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_TERMINATE_AFTER_STOP: + stopStateMachineUpdaters(); + quit(); + break; + + case CMD_STOP: + break; + + case CMD_START: + mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj; + transitionTo(mStartedState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_SET_MULTICAST_FILTER: + mMulticastFiltering = (boolean) msg.obj; + break; + + case DhcpClient.CMD_ON_QUIT: + // Everything is already stopped. + logError("Unexpected CMD_ON_QUIT (already stopped)."); + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + class StoppingState extends State { + @Override + public void enter() { + if (mDhcpClient == null) { + // There's no DHCPv4 for which to wait; proceed to stopped. + deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED)); + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_STOPPING_TO_STOPPED: + transitionTo(mStoppedState); + break; + + case CMD_STOP: + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_ON_QUIT: + mDhcpClient = null; + transitionTo(mStoppedState); + break; + + default: + deferMessage(msg); + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + class StartedState extends State { + @Override + public void enter() { + mStartTimeMillis = SystemClock.elapsedRealtime(); + + if (mConfiguration.mProvisioningTimeoutMs > 0) { + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mProvisioningTimeoutMs; + mProvisioningTimeoutAlarm.schedule(alarmTime); + } + + if (readyToProceed()) { + deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING)); + } else { + // Clear all IPv4 and IPv6 before proceeding to RunningState. + // Clean up any leftover state from an abnormal exit from + // tethering or during an IpClient restart. + stopAllIP(); + } + } + + @Override + public void exit() { + mProvisioningTimeoutAlarm.cancel(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_STARTED_TO_RUNNING: + transitionTo(mRunningState); + break; + + case CMD_STOP: + transitionTo(mStoppingState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + if (readyToProceed()) { + transitionTo(mRunningState); + } + break; + + case EVENT_PROVISIONING_TIMEOUT: + handleProvisioningFailure(); + break; + + default: + // It's safe to process messages out of order because the + // only message that can both + // a) be received at this time and + // b) affect provisioning state + // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). + deferMessage(msg); + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + + private boolean readyToProceed() { + return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address()); + } + } + + class RunningState extends State { + private ConnectivityPacketTracker mPacketTracker; + private boolean mDhcpActionInFlight; + + @Override + public void enter() { + ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); + apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; + apfConfig.multicastFilter = mMulticastFiltering; + // Get the Configuration for ApfFilter from Context + apfConfig.ieee802_3Filter = + mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); + apfConfig.ethTypeBlackList = + mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList); + mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); + // TODO: investigate the effects of any multicast filtering racing/interfering with the + // rest of this IP configuration startup. + if (mApfFilter == null) { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + + mPacketTracker = createPacketTracker(); + if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); + + if (mConfiguration.mEnableIPv6 && !startIPv6()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); + enqueueJumpToStoppingState(); + return; + } + + if (mConfiguration.mEnableIPv4 && !startIPv4()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); + enqueueJumpToStoppingState(); + return; + } + + final InitialConfiguration config = mConfiguration.mInitialConfig; + if ((config != null) && !applyInitialConfig(config)) { + // TODO introduce a new IpManagerEvent constant to distinguish this error case. + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + enqueueJumpToStoppingState(); + return; + } + + if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { + doImmediateProvisioningFailure( + IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); + enqueueJumpToStoppingState(); + return; + } + } + + @Override + public void exit() { + stopDhcpAction(); + + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.stop(); + mIpReachabilityMonitor = null; + } + + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); + mDhcpClient.doQuit(); + } + + if (mPacketTracker != null) { + mPacketTracker.stop(); + mPacketTracker = null; + } + + if (mApfFilter != null) { + mApfFilter.shutdown(); + mApfFilter = null; + } + + resetLinkProperties(); + } + + private void enqueueJumpToStoppingState() { + deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING)); + } + + private ConnectivityPacketTracker createPacketTracker() { + try { + return new ConnectivityPacketTracker( + getHandler(), mInterfaceParams, mConnectivityPacketLog); + } catch (IllegalArgumentException e) { + return null; + } + } + + private void ensureDhcpAction() { + if (!mDhcpActionInFlight) { + mCallback.onPreDhcpAction(); + mDhcpActionInFlight = true; + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mRequestedPreDhcpActionMs; + mDhcpActionTimeoutAlarm.schedule(alarmTime); + } + } + + private void stopDhcpAction() { + mDhcpActionTimeoutAlarm.cancel(); + if (mDhcpActionInFlight) { + mCallback.onPostDhcpAction(); + mDhcpActionInFlight = false; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_RUNNING_TO_STOPPING: + case CMD_STOP: + transitionTo(mStoppingState); + break; + + case CMD_START: + logError("ALERT: START received in StartedState. Please fix caller."); + break; + + case CMD_CONFIRM: + // TODO: Possibly introduce a second type of confirmation + // that both probes (a) on-link neighbors and (b) does + // a DHCPv4 RENEW. We used to do this on Wi-Fi framework + // roams. + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.probeAll(); + } + break; + + case EVENT_PRE_DHCP_ACTION_COMPLETE: + // It's possible to reach here if, for example, someone + // calls completedPreDhcpAction() after provisioning with + // a static IP configuration. + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { + transitionTo(mStoppingState); + } + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_SET_MULTICAST_FILTER: { + mMulticastFiltering = (boolean) msg.obj; + if (mApfFilter != null) { + mApfFilter.setMulticastFilter(mMulticastFiltering); + } else { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + break; + } + + case EVENT_READ_PACKET_FILTER_COMPLETE: { + if (mApfFilter != null) { + mApfFilter.setDataSnapshot((byte[]) msg.obj); + } + mApfDataSnapshotComplete.open(); + break; + } + + case EVENT_DHCPACTION_TIMEOUT: + stopDhcpAction(); + break; + + case DhcpClient.CMD_PRE_DHCP_ACTION: + if (mConfiguration.mRequestedPreDhcpActionMs > 0) { + ensureDhcpAction(); + } else { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { + final LinkAddress ipAddress = (LinkAddress) msg.obj; + if (mInterfaceCtrl.setIPv4Address(ipAddress)) { + mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); + } else { + logError("Failed to set IPv4 address."); + dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, + new LinkProperties(mLinkProperties)); + transitionTo(mStoppingState); + } + break; + } + + // This message is only received when: + // + // a) initial address acquisition succeeds, + // b) renew succeeds or is NAK'd, + // c) rebind succeeds or is NAK'd, or + // c) the lease expires, + // + // but never when initial address acquisition fails. The latter + // condition is now governed by the provisioning timeout. + case DhcpClient.CMD_POST_DHCP_ACTION: + stopDhcpAction(); + + switch (msg.arg1) { + case DhcpClient.DHCP_SUCCESS: + handleIPv4Success((DhcpResults) msg.obj); + break; + case DhcpClient.DHCP_FAILURE: + handleIPv4Failure(); + break; + default: + logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); + } + break; + + case DhcpClient.CMD_ON_QUIT: + // DHCPv4 quit early for some reason. + logError("Unexpected CMD_ON_QUIT."); + mDhcpClient = null; + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + private static class MessageHandlingLogger { + public String processedInState; + public String receivedInState; + + public void reset() { + processedInState = null; + receivedInState = null; + } + + public void handled(State processedIn, IState receivedIn) { + processedInState = processedIn.getClass().getSimpleName(); + receivedInState = receivedIn.getName(); + } + + public String toString() { + return String.format("rcvd_in=%s, proc_in=%s", + receivedInState, processedInState); + } + } + + private static void setNeighborParameters( + INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs) + throws RemoteException, IllegalArgumentException { + Preconditions.checkNotNull(netd); + Preconditions.checkArgument(!TextUtils.isEmpty(ifName)); + Preconditions.checkArgument(numSolicits > 0); + Preconditions.checkArgument(interSolicitIntervalMs > 0); + + for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) { + netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms", + Integer.toString(interSolicitIntervalMs)); + netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit", + Integer.toString(numSolicits)); + } + } + + // TODO: extract out into CollectionUtils. + static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { + for (T t : coll) { + if (fn.test(t)) { + return true; + } + } + return false; + } + + static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { + return !any(coll, not(fn)); + } + + static <T> Predicate<T> not(Predicate<T> fn) { + return (t) -> !fn.test(t); + } + + static <T> String join(String delimiter, Collection<T> coll) { + return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); + } + + static <T> T find(Iterable<T> coll, Predicate<T> fn) { + for (T t: coll) { + if (fn.test(t)) { + return t; + } + } + return null; + } + + static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) { + return coll.stream().filter(fn).collect(Collectors.toList()); + } +} diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java index eb993a4243a9..eb993a4243a9 100644 --- a/services/net/java/android/net/ip/IpNeighborMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java index 761db6822fb4..761db6822fb4 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java index ec833b07e806..08c3f60eff3d 100644 --- a/services/net/java/android/net/util/ConnectivityPacketSummary.java +++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java @@ -16,47 +16,46 @@ package android.net.util; -import static android.net.util.NetworkConstants.ARP_HWTYPE_ETHER; -import static android.net.util.NetworkConstants.ARP_PAYLOAD_LEN; -import static android.net.util.NetworkConstants.ARP_REPLY; -import static android.net.util.NetworkConstants.ARP_REQUEST; -import static android.net.util.NetworkConstants.DHCP4_CLIENT_PORT; -import static android.net.util.NetworkConstants.ETHER_ADDR_LEN; -import static android.net.util.NetworkConstants.ETHER_DST_ADDR_OFFSET; -import static android.net.util.NetworkConstants.ETHER_HEADER_LEN; -import static android.net.util.NetworkConstants.ETHER_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.ETHER_TYPE_ARP; -import static android.net.util.NetworkConstants.ETHER_TYPE_IPV4; -import static android.net.util.NetworkConstants.ETHER_TYPE_IPV6; -import static android.net.util.NetworkConstants.ETHER_TYPE_OFFSET; -import static android.net.util.NetworkConstants.ICMPV6_HEADER_MIN_LEN; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MIN_LENGTH; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MTU; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_SLLA; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_TLLA; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_SOLICITATION; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION; -import static android.net.util.NetworkConstants.IPV4_ADDR_LEN; -import static android.net.util.NetworkConstants.IPV4_DST_ADDR_OFFSET; -import static android.net.util.NetworkConstants.IPV4_FLAGS_OFFSET; -import static android.net.util.NetworkConstants.IPV4_FRAGMENT_MASK; -import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN; -import static android.net.util.NetworkConstants.IPV4_IHL_MASK; -import static android.net.util.NetworkConstants.IPV4_PROTOCOL_OFFSET; -import static android.net.util.NetworkConstants.IPV4_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.IPV6_ADDR_LEN; -import static android.net.util.NetworkConstants.IPV6_HEADER_LEN; -import static android.net.util.NetworkConstants.IPV6_PROTOCOL_OFFSET; -import static android.net.util.NetworkConstants.IPV6_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.UDP_HEADER_LEN; -import static android.net.util.NetworkConstants.asString; -import static android.net.util.NetworkConstants.asUint; import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.IPPROTO_UDP; +import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER; +import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN; +import static com.android.server.util.NetworkStackConstants.ARP_REPLY; +import static com.android.server.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT; +import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN; +import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET; +import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK; +import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; +import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK; +import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN; +import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN; + import android.net.MacAddress; import android.net.dhcp.DhcpPacket; @@ -412,4 +411,25 @@ public class ConnectivityPacketSummary { final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x"; return String.format(MAC48_FORMAT, printableBytes); } + + /** + * Convenience method to convert an int to a String. + */ + public static String asString(int i) { + return Integer.toString(i); + } + + /** + * Convenience method to read a byte as an unsigned int. + */ + public static int asUint(byte b) { + return (b & 0xff); + } + + /** + * Convenience method to read a short as an unsigned int. + */ + public static int asUint(short s) { + return (s & 0xffff); + } } diff --git a/services/net/java/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java index 8bbf449f6374..8bbf449f6374 100644 --- a/services/net/java/android/net/util/FdEventsReader.java +++ b/packages/NetworkStack/src/android/net/util/FdEventsReader.java diff --git a/services/net/java/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java index 4aec6b6753a6..4aec6b6753a6 100644 --- a/services/net/java/android/net/util/PacketReader.java +++ b/packages/NetworkStack/src/android/net/util/PacketReader.java diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index cca71e759bdd..4080ddf9e73f 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -39,6 +39,8 @@ import android.net.dhcp.DhcpServer; import android.net.dhcp.DhcpServingParams; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; +import android.net.ip.IpClient; import android.net.shared.PrivateDnsConfig; import android.net.util.SharedLog; import android.os.IBinder; @@ -50,7 +52,11 @@ import com.android.server.connectivity.NetworkMonitor; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; /** * Android service used to start the network stack when bound to via an intent. @@ -80,6 +86,8 @@ public class NetworkStackService extends Service { private static final int NUM_VALIDATION_LOG_LINES = 20; private final Context mContext; private final ConnectivityManager mCm; + @GuardedBy("mIpClients") + private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>(); private static final int MAX_VALIDATION_LOGS = 10; @GuardedBy("mValidationLogs") @@ -138,6 +146,24 @@ public class NetworkStackService extends Service { } @Override + public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException { + final IpClient ipClient = new IpClient(mContext, ifName, cb); + + synchronized (mIpClients) { + final Iterator<WeakReference<IpClient>> it = mIpClients.iterator(); + while (it.hasNext()) { + final IpClient ipc = it.next().get(); + if (ipc == null) { + it.remove(); + } + } + mIpClients.add(new WeakReference<>(ipClient)); + } + + cb.onIpClientCreated(ipClient.makeConnector()); + } + + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { checkDumpPermission(); @@ -145,6 +171,33 @@ public class NetworkStackService extends Service { pw.println("NetworkStack logs:"); mLog.dump(fd, pw, args); + // Dump full IpClient logs for non-GCed clients + pw.println(); + pw.println("Recently active IpClient logs:"); + final ArrayList<IpClient> ipClients = new ArrayList<>(); + final HashSet<String> dumpedIpClientIfaces = new HashSet<>(); + synchronized (mIpClients) { + for (WeakReference<IpClient> ipcRef : mIpClients) { + final IpClient ipc = ipcRef.get(); + if (ipc != null) { + ipClients.add(ipc); + } + } + } + + for (IpClient ipc : ipClients) { + pw.println(ipc.getName()); + pw.increaseIndent(); + ipc.dump(fd, pw, args); + pw.decreaseIndent(); + dumpedIpClientIfaces.add(ipc.getInterfaceName()); + } + + // State machine and connectivity metrics logs are kept for GCed IpClients + pw.println(); + pw.println("Other IpClient logs:"); + IpClient.dumpAllLogs(fout, dumpedIpClientIfaces); + pw.println(); pw.println("Validation logs (most recent first):"); synchronized (mValidationLogs) { diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index a3d7852c9f4a..6b31b82ec3cc 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -247,7 +247,6 @@ public class NetworkMonitor extends StateMachine { private final TelephonyManager mTelephonyManager; private final WifiManager mWifiManager; private final ConnectivityManager mCm; - private final NetworkRequest mDefaultRequest; private final IpConnectivityLog mMetricsLog; private final Dependencies mDependencies; @@ -336,7 +335,6 @@ public class NetworkMonitor extends StateMachine { mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - mDefaultRequest = defaultRequest; // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); @@ -486,8 +484,7 @@ public class NetworkMonitor extends StateMachine { } private boolean isValidationRequired() { - return NetworkMonitorUtils.isValidationRequired( - mDefaultRequest.networkCapabilities, mNetworkCapabilities); + return NetworkMonitorUtils.isValidationRequired(mNetworkCapabilities); } diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java index bb5900c53e52..eedaf30c055f 100644 --- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java +++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java @@ -32,12 +32,103 @@ public final class NetworkStackConstants { public static final int IPV4_MAX_MTU = 65_535; /** + * Ethernet constants. + * + * See also: + * - https://tools.ietf.org/html/rfc894 + * - https://tools.ietf.org/html/rfc2464 + * - https://tools.ietf.org/html/rfc7042 + * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml + * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml + */ + public static final int ETHER_DST_ADDR_OFFSET = 0; + public static final int ETHER_SRC_ADDR_OFFSET = 6; + public static final int ETHER_ADDR_LEN = 6; + public static final int ETHER_TYPE_OFFSET = 12; + public static final int ETHER_TYPE_LENGTH = 2; + public static final int ETHER_TYPE_ARP = 0x0806; + public static final int ETHER_TYPE_IPV4 = 0x0800; + public static final int ETHER_TYPE_IPV6 = 0x86dd; + public static final int ETHER_HEADER_LEN = 14; + + /** + * ARP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc826 + * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml + */ + public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. + public static final int ARP_REQUEST = 1; + public static final int ARP_REPLY = 2; + public static final int ARP_HWTYPE_RESERVED_LO = 0; + public static final int ARP_HWTYPE_ETHER = 1; + public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; + + /** + * IPv4 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc791 + */ + public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_IHL_MASK = 0xf; + public static final int IPV4_FLAGS_OFFSET = 6; + public static final int IPV4_FRAGMENT_MASK = 0x1fff; + public static final int IPV4_PROTOCOL_OFFSET = 9; + public static final int IPV4_SRC_ADDR_OFFSET = 12; + public static final int IPV4_DST_ADDR_OFFSET = 16; + public static final int IPV4_ADDR_LEN = 4; + + /** + * IPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2460 + */ + public static final int IPV6_ADDR_LEN = 16; + public static final int IPV6_HEADER_LEN = 40; + public static final int IPV6_PROTOCOL_OFFSET = 6; + public static final int IPV6_SRC_ADDR_OFFSET = 8; + public static final int IPV6_DST_ADDR_OFFSET = 24; + + /** + * ICMPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc4443 + * - https://tools.ietf.org/html/rfc4861 + */ + public static final int ICMPV6_HEADER_MIN_LEN = 4; + public static final int ICMPV6_ECHO_REPLY_TYPE = 129; + public static final int ICMPV6_ECHO_REQUEST_TYPE = 128; + public static final int ICMPV6_ROUTER_SOLICITATION = 133; + public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; + public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; + public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; + public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; + public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; + public static final int ICMPV6_ND_OPTION_SLLA = 1; + public static final int ICMPV6_ND_OPTION_TLLA = 2; + public static final int ICMPV6_ND_OPTION_MTU = 5; + + /** + * UDP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc768 + */ + public static final int UDP_HEADER_LEN = 8; + + + /** * DHCP constants. * * See also: * - https://tools.ietf.org/html/rfc2131 */ public static final int INFINITE_LEASE = 0xffffffff; + public static final int DHCP4_CLIENT_PORT = 68; private NetworkStackConstants() { throw new UnsupportedOperationException("This class is not to be instantiated"); diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp index bd7ff2a75703..45fa2dc2f383 100644 --- a/packages/NetworkStack/tests/Android.bp +++ b/packages/NetworkStack/tests/Android.bp @@ -16,9 +16,12 @@ android_test { name: "NetworkStackTests", + certificate: "platform", srcs: ["src/**/*.java"], + resource_dirs: ["res"], static_libs: [ "android-support-test", + "frameworks-base-testutils", "mockito-target-extended-minus-junit4", "NetworkStackLib", "testables", @@ -26,10 +29,70 @@ android_test { libs: [ "android.test.runner", "android.test.base", + "android.test.mock", ], jni_libs: [ // For mockito extended "libdexmakerjvmtiagent", "libstaticjvmtiagent", - ] -}
\ No newline at end of file + // For ApfTest + "libartbase", + "libbacktrace", + "libbase", + "libbinder", + "libbinderthreadstate", + "libc++", + "libcrypto", + "libcutils", + "libdexfile", + "libhidl-gen-utils", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "liblog", + "liblzma", + "libnativehelper", + "libnetworkstacktestsjni", + "libpackagelistparser", + "libpcre2", + "libprocessgroup", + "libselinux", + "libui", + "libutils", + "libvintf", + "libvndksupport", + "libtinyxml2", + "libunwindstack", + "libutilscallstack", + "libziparchive", + "libz", + "netd_aidl_interface-cpp", + ], +} + +cc_library_shared { + name: "libnetworkstacktestsjni", + srcs: [ + "jni/**/*.cpp" + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + include_dirs: [ + "hardware/google/apf", + ], + shared_libs: [ + "libbinder", + "liblog", + "libcutils", + "libnativehelper", + "netd_aidl_interface-cpp", + ], + static_libs: [ + "libapf", + "libpcap", + ], + +} diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml index 8b8474f57e28..9cb2c21cc399 100644 --- a/packages/NetworkStack/tests/AndroidManifest.xml +++ b/packages/NetworkStack/tests/AndroidManifest.xml @@ -15,6 +15,35 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.networkstack.tests"> + + <uses-permission android:name="android.permission.READ_LOGS" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.BROADCAST_STICKY" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> + <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS" /> + <uses-permission android:name="android.permission.GET_DETAILED_TASKS" /> + <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> + <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> + <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> + <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> + <uses-permission android:name="android.permission.NETWORK_STACK" /> + <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/net/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp index 4222adf9e06b..4222adf9e06b 100644 --- a/tests/net/jni/apf_jni.cpp +++ b/packages/NetworkStack/tests/jni/apf_jni.cpp diff --git a/tests/net/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap Binary files differindex 963165f19f73..963165f19f73 100644 --- a/tests/net/res/raw/apf.pcap +++ b/packages/NetworkStack/tests/res/raw/apf.pcap diff --git a/tests/net/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap Binary files differindex 6f69c4add0f8..6f69c4add0f8 100644 --- a/tests/net/res/raw/apfPcap.pcap +++ b/packages/NetworkStack/tests/res/raw/apfPcap.pcap diff --git a/tests/net/java/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index 3c3e7ce3b12a..f76e41217c2a 100644 --- a/tests/net/java/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -16,8 +16,6 @@ package android.net.apf; -import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; import static android.system.OsConstants.AF_UNIX; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; @@ -28,12 +26,14 @@ import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_STREAM; import static com.android.internal.util.BitUtils.bytesToBEInt; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; @@ -42,10 +42,14 @@ import android.net.LinkProperties; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; +import android.net.ip.IIpClientCallbacks; +import android.net.ip.IpClient; +import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.net.ip.IpClientCallbacks; import android.net.metrics.IpConnectivityLog; import android.net.metrics.RaEvent; import android.net.util.InterfaceParams; +import android.net.util.SharedLog; import android.os.ConditionVariable; import android.os.Parcelable; import android.os.SystemClock; @@ -57,8 +61,9 @@ import android.system.Os; import android.text.format.DateUtils; import android.util.Log; -import com.android.frameworks.tests.net.R; import com.android.internal.util.HexDump; +import com.android.server.networkstack.tests.R; +import com.android.server.util.NetworkStackConstants; import libcore.io.IoUtils; import libcore.io.Streams; @@ -100,7 +105,7 @@ public class ApfTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); // Load up native shared library containing APF interpreter exposed via JNI. - System.loadLibrary("frameworksnettestsjni"); + System.loadLibrary("networkstacktestsjni"); } private static final String TAG = "ApfTest"; @@ -915,10 +920,14 @@ public class ApfTest { HexDump.toHexString(data, false), result); } - private class MockIpClientCallback extends IpClientCallbacks { + private class MockIpClientCallback extends IpClientCallbacksWrapper { private final ConditionVariable mGotApfProgram = new ConditionVariable(); private byte[] mLastApfProgram; + MockIpClientCallback() { + super(mock(IIpClientCallbacks.class), mock(SharedLog.class)); + } + @Override public void installPacketFilter(byte[] filter) { mLastApfProgram = filter; @@ -946,7 +955,7 @@ public class ApfTest { private final long mFixedTimeMs = SystemClock.elapsedRealtime(); public TestApfFilter(Context context, ApfConfiguration config, - IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception { + IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception { super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log); } @@ -1075,8 +1084,8 @@ public class ApfTest { private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0}; // Helper to initialize a default apfFilter. - private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config) - throws Exception { + private ApfFilter setupApfFilter( + IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception { LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); LinkProperties lp = new LinkProperties(); lp.addLinkAddress(link); @@ -1294,7 +1303,7 @@ public class ApfTest { // However, we should still let through all other ICMPv6 types. ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone()); - raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT); + raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT); assertPass(ipClientCallback.getApfProgram(), raPacket.array()); // Now wake up from doze mode to ensure that we no longer drop the packets. diff --git a/tests/net/java/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java index 5d57cde22fb1..5d57cde22fb1 100644 --- a/tests/net/java/android/net/apf/Bpf2Apf.java +++ b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java index a592809618e6..a592809618e6 100644 --- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index 5110ce1830b3..f21809fdbc1c 100644 --- a/tests/net/java/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -16,10 +16,13 @@ package android.net.ip; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; @@ -87,7 +90,7 @@ public class IpClientTest { @Mock private INetworkManagementService mNMService; @Mock private INetd mNetd; @Mock private Resources mResources; - @Mock private IpClientCallbacks mCb; + @Mock private IIpClientCallbacks mCb; @Mock private AlarmManager mAlarm; @Mock private IpClient.Dependencies mDependecies; private MockContentResolver mContentResolver; @@ -209,7 +212,8 @@ public class IpClientTest { verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) - .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); + .onLinkPropertiesChange(argThat( + lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface)))); } @Test @@ -254,13 +258,15 @@ public class IpClientTest { mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr])); LinkProperties want = linkproperties(links(addresses), routes(prefixes)); want.setInterfaceName(iface); - verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want)); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat( + lp -> fromStableParcelable(lp).equals(want))); ipc.shutdown(); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) - .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); + .onLinkPropertiesChange(argThat( + lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface)))); } @Test @@ -492,11 +498,11 @@ public class IpClientTest { List<String> list3 = Arrays.asList("bar", "baz"); List<String> list4 = Arrays.asList("foo", "bar", "baz"); - assertTrue(IpClient.all(list1, (x) -> false)); - assertFalse(IpClient.all(list2, (x) -> false)); - assertTrue(IpClient.all(list3, (x) -> true)); - assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f')); - assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f')); + assertTrue(InitialConfiguration.all(list1, (x) -> false)); + assertFalse(InitialConfiguration.all(list2, (x) -> false)); + assertTrue(InitialConfiguration.all(list3, (x) -> true)); + assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f')); } @Test @@ -506,11 +512,11 @@ public class IpClientTest { List<String> list3 = Arrays.asList("bar", "baz"); List<String> list4 = Arrays.asList("foo", "bar", "baz"); - assertFalse(IpClient.any(list1, (x) -> true)); - assertTrue(IpClient.any(list2, (x) -> true)); - assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f')); - assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f')); - assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.any(list1, (x) -> true)); + assertTrue(InitialConfiguration.any(list2, (x) -> true)); + assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f')); + assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f')); } @Test diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java index e3b5ddf6f4cf..e3b5ddf6f4cf 100644 --- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java index dfaf52a953c7..dfaf52a953c7 100644 --- a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java +++ b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java diff --git a/tests/net/java/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java index dced7435ee74..dced7435ee74 100644 --- a/tests/net/java/android/net/util/PacketReaderTest.java +++ b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 2d7471d9aca5..a9ff21fef99c 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -1962,8 +1962,7 @@ public class BugreportProgressService extends Service { } @Override - public void onFinished(long durationMs, String title, String description) - throws RemoteException { + public void onFinished() throws RemoteException { // TODO(b/111441001): implement } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 330ee8f9ab81..326147df184d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -527,7 +527,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting void doUpdateMobileControllers() { - List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList(); + List<SubscriptionInfo> subscriptions = mSubscriptionManager + .getActiveSubscriptionInfoList(true); if (subscriptions == null) { subscriptions = Collections.emptyList(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 35f0dba12cb9..55b4d27ff354 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -182,6 +182,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { subs.add(subscription); } when(mMockSm.getActiveSubscriptionInfoList()).thenReturn(subs); + when(mMockSm.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subs); mNetworkController.doUpdateMobileControllers(); } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 5ea3390d4e46..b60dd0f52c73 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -208,6 +208,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private int mErrorRecoveryRetryCounter; private final int mSystemUiUid; + private boolean mIsHearingAidProfileSupported; + // Save a ProfileServiceConnections object for each of the bound // bluetooth profile services private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>(); @@ -391,13 +393,19 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>(); mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>(); + mIsHearingAidProfileSupported = context.getResources() + .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported); + // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils - boolean isHearingAidEnabled; String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS); if (!TextUtils.isEmpty(value)) { - isHearingAidEnabled = Boolean.parseBoolean(value); + boolean isHearingAidEnabled = Boolean.parseBoolean(value); Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled); FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled); + if (isHearingAidEnabled && !mIsHearingAidProfileSupported) { + // Overwrite to enable support by FeatureFlag + mIsHearingAidProfileSupported = true; + } } IntentFilter filter = new IntentFilter(); @@ -679,6 +687,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return false; } + @Override + public boolean isHearingAidProfileSupported() { + return mIsHearingAidProfileSupported; + } + // Monitor change of BLE scan only mode settings. private void registerForBleScanModeChange() { ContentObserver contentObserver = new ContentObserver(null) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index fae7a8d7bfab..fda7279c45d1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -73,6 +73,7 @@ import android.net.INetworkStatsService; import android.net.LinkProperties; import android.net.LinkProperties.CompareResult; import android.net.MatchAllNetworkSpecifier; +import android.net.NattSocketKeepalive; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; @@ -100,7 +101,7 @@ import android.net.netlink.InetDiagMessage; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -2640,8 +2641,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private boolean networkRequiresValidation(NetworkAgentInfo nai) { - return isValidationRequired( - mDefaultRequest.networkCapabilities, nai.networkCapabilities); + return isValidationRequired(nai.networkCapabilities); } private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) { @@ -6186,6 +6186,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId, + int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr, + String dstAddr) { + enforceKeepalivePermission(); + mKeepaliveTracker.startNattKeepalive( + getNetworkAgentInfoForNetwork(network), fd, resourceId, + intervalSeconds, messenger, binder, + srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT); + } + + @Override public void stopKeepalive(Network network, int slot) { mHandler.sendMessage(mHandler.obtainMessage( NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network)); diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 126bf6556538..371276fbd2ae 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -44,7 +44,7 @@ import android.net.LinkAddress; import android.net.Network; import android.net.NetworkUtils; import android.net.TrafficStats; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 5ef3fe49da5d..7f61dd120c7f 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -46,7 +46,9 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; +import android.net.InetAddresses; import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; import android.net.INetworkManagementEventObserver; import android.net.ITetheringStatsProvider; import android.net.InterfaceConfiguration; @@ -60,7 +62,7 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.TetherStatsParcel; import android.net.UidRange; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; @@ -204,6 +206,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private INetd mNetdService; + private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener; + private IBatteryStats mBatteryStats; private final Thread mThread; @@ -321,6 +325,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub mDaemonHandler = new Handler(FgThread.get().getLooper()); + mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); + // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -339,6 +345,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub mFgHandler = null; mThread = null; mServices = null; + mNetdUnsolicitedEventListener = null; } static NetworkManagementService create(Context context, String socket, SystemServices services) @@ -445,7 +452,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub // our sanity-checking state. mActiveAlerts.remove(iface); mActiveQuotas.remove(iface); - invokeForAllObservers(o -> o.interfaceRemoved(iface)); } @@ -547,7 +553,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } // No current code examines the interface parameter in a global alert. Just pass null. - notifyLimitReached(LIMIT_GLOBAL_ALERT, null); + mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null)); } } @@ -578,6 +584,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub private void connectNativeNetdService() { mNetdService = mServices.getNetd(); + try { + mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener); + if (DBG) Slog.d(TAG, "Register unsolicited event listener"); + } catch (RemoteException | ServiceSpecificException e) { + Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e); + } } /** @@ -704,14 +716,96 @@ public class NetworkManagementService extends INetworkManagementService.Stub /** * Notify our observers of a route change. */ - private void notifyRouteChange(String action, RouteInfo route) { - if (action.equals("updated")) { + private void notifyRouteChange(boolean updated, RouteInfo route) { + if (updated) { invokeForAllObservers(o -> o.routeUpdated(route)); } else { invokeForAllObservers(o -> o.routeRemoved(route)); } } + private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) throws RemoteException { + final long timestampNanos; + if (timestamp <= 0) { + timestampNanos = SystemClock.elapsedRealtimeNanos(); + } else { + timestampNanos = timestamp; + } + mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, + isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + timestampNanos, uid, false)); + } + + @Override + public void onQuotaLimitReached(String alertName, String ifName) + throws RemoteException { + mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, + long lifetime, String[] servers) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); + } + + @Override + public void onInterfaceAdded(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); + } + + @Override + public void onInterfaceRemoved(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) + throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) + throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); + } + + @Override + public void onRouteChanged(boolean updated, + String route, String gateway, String ifName) throws RemoteException { + final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), + ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), + ifName); + mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't need to post to mDaemonHandler because the only thing + // that notifyCleartextNetwork does is post to a handler + ActivityManager.getService().notifyCleartextNetwork(uid, + HexDump.hexStringToByteArray(hex)); + } + } + // // Netd Callback handling // @@ -899,7 +993,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub InetAddress gateway = null; if (via != null) gateway = InetAddress.parseNumericAddress(via); RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev); - notifyRouteChange(cooked[2], route); + notifyRouteChange(cooked[2].equals("updated"), route); return true; } catch (IllegalArgumentException e) {} } @@ -1362,13 +1456,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (ConnectivityManager.isNetworkTypeMobile(type)) { mNetworkActive = false; } - mDaemonHandler.post(new Runnable() { - @Override public void run() { - notifyInterfaceClassActivity(type, - DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, - SystemClock.elapsedRealtimeNanos(), -1, false); - } - }); + mDaemonHandler.post(() -> notifyInterfaceClassActivity(type, + DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + SystemClock.elapsedRealtimeNanos(), -1, false)); } } @@ -1391,13 +1481,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(e); } mActiveIdleTimers.remove(iface); - mDaemonHandler.post(new Runnable() { - @Override public void run() { - notifyInterfaceClassActivity(params.type, - DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, - SystemClock.elapsedRealtimeNanos(), -1, false); - } - }); + mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type, + DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + SystemClock.elapsedRealtimeNanos(), -1, false)); } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 122112b7a7d4..1798f388ab27 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2116,6 +2116,16 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRECISE_PHONE_STATE, null); } + if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); + } + + if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null); + } + return true; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ec7947e370e5..f59f188abcdd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -89,7 +89,6 @@ import static android.os.Process.SCHED_OTHER; import static android.os.Process.SCHED_RESET_ON_FORK; import static android.os.Process.SE_UID; import static android.os.Process.SHELL_UID; -import static android.os.Process.SIGNAL_QUIT; import static android.os.Process.SIGNAL_USR1; import static android.os.Process.SYSTEM_UID; import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE; @@ -98,6 +97,7 @@ import static android.os.Process.THREAD_GROUP_RESTRICTED; import static android.os.Process.THREAD_GROUP_TOP_APP; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static android.os.Process.ZYGOTE_PROCESS; import static android.os.Process.getFreeMemory; import static android.os.Process.getTotalMemory; import static android.os.Process.isThreadInProcess; @@ -112,7 +112,6 @@ import static android.os.Process.setProcessGroup; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; import static android.os.Process.startWebView; -import static android.os.Process.zygoteProcess; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES; import static android.provider.Settings.Global.DEBUG_APP; @@ -127,6 +126,12 @@ import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICAT import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_NONE; +import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; + import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; @@ -197,19 +202,15 @@ import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; -import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.am.MemoryStatUtil.hasMemcg; +import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE; -import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; -import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; -import static android.view.WindowManager.TRANSIT_TASK_OPEN; -import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; -import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; +import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -326,7 +327,6 @@ import android.os.Debug; import android.os.DropBoxManager; import android.os.Environment; import android.os.FactoryTest; -import android.os.FileObserver; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -416,12 +416,12 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BinderInternal; -import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.os.ByteTransferPipe; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; +import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.telephony.TelephonyIntents; @@ -448,17 +448,16 @@ import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.ThreadPriorityBooster; import com.android.server.Watchdog; -import com.android.server.am.ActivityStack.ActivityState; -import com.android.server.am.MemoryStatUtil.MemoryStat; -import com.android.server.am.ActivityManagerServiceProto; import com.android.server.am.ActivityManagerServiceDumpActivitiesProto; import com.android.server.am.ActivityManagerServiceDumpBroadcastsProto; import com.android.server.am.ActivityManagerServiceDumpProcessesProto; import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto; import com.android.server.am.ActivityManagerServiceDumpServicesProto; +import com.android.server.am.ActivityStack.ActivityState; import com.android.server.am.GrantUriProto; import com.android.server.am.ImportanceTokenProto; import com.android.server.am.MemInfoDumpProto; +import com.android.server.am.MemoryStatUtil.MemoryStat; import com.android.server.am.NeededUriGrantsProto; import com.android.server.am.ProcessOomProto; import com.android.server.am.ProcessToGcProto; @@ -475,12 +474,12 @@ import com.android.server.wm.WindowManagerService; import dalvik.system.VMRuntime; -import libcore.io.IoUtils; -import libcore.util.EmptyArray; - import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import libcore.io.IoUtils; +import libcore.util.EmptyArray; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -2958,7 +2957,7 @@ public class ActivityManagerService extends IActivityManager.Stub ? Collections.emptyList() : Arrays.asList(exemptions.split(",")); } - if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) { + if (!ZYGOTE_PROCESS.setApiBlacklistExemptions(mExemptions)) { Slog.e(TAG, "Failed to set API blacklist exemptions!"); // leave mExemptionsStr as is, so we don't try to send the same list again. mExemptions = Collections.emptyList(); @@ -2971,7 +2970,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (logSampleRate != -1 && logSampleRate != mLogSampleRate) { mLogSampleRate = logSampleRate; - zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate); + ZYGOTE_PROCESS.setHiddenApiAccessLogSampleRate(mLogSampleRate); } mPolicy = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY); } @@ -7882,7 +7881,7 @@ public class ActivityManagerService extends IActivityManager.Stub ArraySet<String> completedIsas = new ArraySet<String>(); for (String abi : Build.SUPPORTED_ABIS) { - zygoteProcess.establishZygoteConnectionForAbi(abi); + ZYGOTE_PROCESS.establishZygoteConnectionForAbi(abi); final String instructionSet = VMRuntime.getInstructionSet(abi); if (!completedIsas.contains(instructionSet)) { try { diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index ac745985417e..79b56c6027f8 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -20,7 +20,6 @@ import android.content.Context; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; import android.net.INetdEventCallback; -import android.net.ip.IpClient; import android.net.metrics.ApfProgramEvent; import android.net.metrics.IpConnectivityLog; import android.os.Binder; @@ -270,8 +269,6 @@ final public class IpConnectivityMetrics extends SystemService { // Dump the rolling buffer of metrics event and pretty print events using a human readable // format. Also print network dns/connect statistics and default network event time series. static final String CMD_LIST = "list"; - // Dump all IpClient logs ("ipclient"). - static final String CMD_IPCLIENT = IpClient.DUMP_ARG; // By default any other argument will fall into the default case which is the equivalent // of calling both the "list" and "ipclient" commands. This includes most notably bug // reports collected by dumpsys.cpp with the "-a" argument. @@ -295,20 +292,9 @@ final public class IpConnectivityMetrics extends SystemService { case CMD_PROTO: cmdListAsProto(pw); return; - case CMD_IPCLIENT: { - final String[] ipclientArgs = ((args != null) && (args.length > 1)) - ? Arrays.copyOfRange(args, 1, args.length) - : null; - IpClient.dumpAllLogs(pw, ipclientArgs); - return; - } case CMD_LIST: - cmdList(pw); - return; default: cmdList(pw); - pw.println(""); - IpClient.dumpAllLogs(pw, null); return; } } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 8a3cdcad0230..1559ba8ba883 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -16,40 +16,49 @@ package com.android.server.connectivity; -import com.android.internal.util.HexDump; -import com.android.internal.util.IndentingPrintWriter; -import com.android.server.connectivity.NetworkAgentInfo; -import android.net.ConnectivityManager; +// TODO: Clean up imports and remove references of PacketKeepalive constants. + +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL; +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS; +import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK; +import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL; +import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT; +import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE; +import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS; +import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; +import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; +import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.ConnectivityManager.PacketKeepalive; import android.net.KeepalivePacketData; -import android.net.LinkAddress; import android.net.NetworkAgent; import android.net.NetworkUtils; import android.net.util.IpUtils; import android.os.Binder; -import android.os.IBinder; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.Process; import android.os.RemoteException; -import android.system.OsConstants; +import android.system.ErrnoException; +import android.system.Os; import android.util.Log; import android.util.Pair; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.net.Inet4Address; -import java.net.Inet6Address; +import com.android.internal.util.HexDump; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.FileDescriptor; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.ArrayList; import java.util.HashMap; -import static android.net.ConnectivityManager.PacketKeepalive.*; -import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; -import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; - /** * Manages packet keepalive requests. * @@ -300,7 +309,9 @@ public class KeepaliveTracker { } } - public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) { + /** Handle keepalive events from lower layer. */ + public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai, + @NonNull Message message) { int slot = message.arg1; int reason = message.arg2; @@ -330,8 +341,18 @@ public class KeepaliveTracker { } } - public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, - IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { + /** + * Called when requesting that keepalives be started on a IPsec NAT-T socket. See + * {@link android.net.SocketKeepalive}. + **/ + public void startNattKeepalive(@Nullable NetworkAgentInfo nai, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull String srcAddrString, + int srcPort, + @NonNull String dstAddrString, + int dstPort) { if (nai == null) { notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); return; @@ -360,6 +381,56 @@ public class KeepaliveTracker { NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget(); } + /** + * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is + * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the + * resource index bound to the {@link UdpEncapsulationSocket} when creating by + * {@link com.android.server.IpSecService} to verify whether the given + * {@link UdpEncapsulationSocket} is legitimate. + **/ + public void startNattKeepalive(@Nullable NetworkAgentInfo nai, + @Nullable FileDescriptor fd, + int resourceId, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull String srcAddrString, + @NonNull String dstAddrString, + int dstPort) { + // Ensure that the socket is created by IpSecService. + if (!isNattKeepaliveSocketValid(fd, resourceId)) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET); + } + + // Get src port to adopt old API. + int srcPort = 0; + try { + final SocketAddress srcSockAddr = Os.getsockname(fd); + srcPort = ((InetSocketAddress) srcSockAddr).getPort(); + } catch (ErrnoException e) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET); + } + + // Forward request to old API. + startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort, + dstAddrString, dstPort); + } + + /** + * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid. + **/ + public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) { + // TODO: 1. confirm whether the fd is called from system api or created by IpSecService. + // 2. If the fd is created from the system api, check that it's bounded. And + // call dup to keep the fd open. + // 3. If the fd is created from IpSecService, check if the resource ID is valid. And + // hold the resource needed in IpSecService. + if (null == fd) { + return false; + } + return true; + } + public void dump(IndentingPrintWriter pw) { pw.println("Packet keepalives:"); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java index e2415911e929..bee7a8b7166e 100644 --- a/services/core/java/com/android/server/os/BugreportManagerService.java +++ b/services/core/java/com/android/server/os/BugreportManagerService.java @@ -37,7 +37,6 @@ public class BugreportManagerService extends SystemService { @Override public void onStart() { mService = new BugreportManagerServiceImpl(getContext()); - // TODO(b/111441001): Needs sepolicy to be submitted first. - // publishBinderService(Context.BUGREPORT_SERVICE, mService); + publishBinderService(Context.BUGREPORT_SERVICE, mService); } } diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 1178cc18f828..f736056c5c7f 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -44,6 +44,7 @@ import java.io.FileDescriptor; */ class BugreportManagerServiceImpl extends IDumpstate.Stub { private static final String TAG = "BugreportManagerService"; + private static final String BUGREPORT_SERVICE = "bugreportd"; private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; private IDumpstate mDs = null; @@ -64,6 +65,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { throw new UnsupportedOperationException("setListener is not allowed on this service"); } + // TODO(b/111441001): Intercept onFinished here in system server and shutdown + // the bugreportd service. @Override @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(int callingUidUnused, String callingPackage, @@ -84,6 +87,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { bugreportFd, screenshotFd, bugreportMode, listener); } + @Override + @RequiresPermission(android.Manifest.permission.DUMP) + public void cancelBugreport() throws RemoteException { + // This tells init to cancel bugreportd service. + SystemProperties.set("ctl.stop", BUGREPORT_SERVICE); + mDs = null; + } + private boolean validate(@BugreportParams.BugreportMode int mode) { if (mode != BugreportParams.BUGREPORT_MODE_FULL && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE @@ -107,7 +118,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { */ private IDumpstate getDumpstateService() { // Start bugreport service. - SystemProperties.set("ctl.start", "bugreport"); + SystemProperties.set("ctl.start", BUGREPORT_SERVICE); IDumpstate ds = null; boolean timedOut = false; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index f0807b9b36d3..a48104a16a9f 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -589,6 +589,14 @@ public class Installer extends SystemService { throw new InstallerException("Invalid instruction set: " + instructionSet); } + public boolean compileLayouts(String apkPath, String packageName, String outDexFile, int uid) { + try { + return mInstalld.compileLayouts(apkPath, packageName, outDexFile, uid); + } catch (RemoteException e) { + return false; + } + } + public static class InstallerException extends Exception { public InstallerException(String detailMessage) { super(detailMessage); diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS new file mode 100644 index 000000000000..60d792530101 --- /dev/null +++ b/services/core/java/com/android/server/pm/OWNERS @@ -0,0 +1,76 @@ +hackbod@android.com +hackbod@google.com +jsharkey@android.com +jsharkey@google.com +narayan@google.com +patb@google.com +svetoslavganov@android.com +svetoslavganov@google.com +toddke@android.com +toddke@google.com + +# dex +per-file AbstractStatsBase.java = agampe@google.com +per-file AbstractStatsBase.java = calin@google.com +per-file AbstractStatsBase.java = ngeoffray@google.com +per-file BackgroundDexOptService.java = agampe@google.com +per-file BackgroundDexOptService.java = calin@google.com +per-file BackgroundDexOptService.java = ngeoffray@google.com +per-file CompilerStats.java = agampe@google.com +per-file CompilerStats.java = calin@google.com +per-file CompilerStats.java = ngeoffray@google.com +per-file InstructionSets.java = agampe@google.com +per-file InstructionSets.java = calin@google.com +per-file InstructionSets.java = ngeoffray@google.com +per-file OtaDexoptService.java = agampe@google.com +per-file OtaDexoptService.java = calin@google.com +per-file OtaDexoptService.java = ngeoffray@google.com +per-file OtaDexoptShellCommand.java = agampe@google.com +per-file OtaDexoptShellCommand.java = calin@google.com +per-file OtaDexoptShellCommand.java = ngeoffray@google.com +per-file PackageDexOptimizer.java = agampe@google.com +per-file PackageDexOptimizer.java = calin@google.com +per-file PackageDexOptimizer.java = ngeoffray@google.com +per-file PackageManagerServiceCompilerMapping.java = agampe@google.com +per-file PackageManagerServiceCompilerMapping.java = calin@google.com +per-file PackageManagerServiceCompilerMapping.java = ngeoffray@google.com +per-file PackageUsage.java = agampe@google.com +per-file PackageUsage.java = calin@google.com +per-file PackageUsage.java = ngeoffray@google.com + +# multi user / cross profile +per-file CrossProfileAppsServiceImpl.java = omakoto@google.com +per-file CrossProfileAppsServiceImpl.java = yamasani@google.com +per-file CrossProfileAppsService.java = omakoto@google.com +per-file CrossProfileAppsService.java = yamasani@google.com +per-file CrossProfileIntentFilter.java = omakoto@google.com +per-file CrossProfileIntentFilter.java = yamasani@google.com +per-file CrossProfileIntentResolver.java = omakoto@google.com +per-file CrossProfileIntentResolver.java = yamasani@google.com +per-file UserManagerService.java = omakoto@google.com +per-file UserManagerService.java = yamasani@google.com +per-file UserRestrictionsUtils.java = omakoto@google.com +per-file UserRestrictionsUtils.java = yamasani@google.com + +# security +per-file KeySetHandle.java = cbrubaker@google.com +per-file KeySetManagerService.java = cbrubaker@google.com +per-file PackageKeySetData.java = cbrubaker@google.com +per-file PackageSignatures.java = cbrubaker@google.com +per-file SELinuxMMAC.java = cbrubaker@google.com + +# shortcuts +per-file LauncherAppsService.java = omakoto@google.com +per-file ShareTargetInfo.java = omakoto@google.com +per-file ShortcutBitmapSaver.java = omakoto@google.com +per-file ShortcutDumpFiles.java = omakoto@google.com +per-file ShortcutLauncher.java = omakoto@google.com +per-file ShortcutNonPersistentUser.java = omakoto@google.com +per-file ShortcutPackage.java = omakoto@google.com +per-file ShortcutPackageInfo.java = omakoto@google.com +per-file ShortcutPackageItem.java = omakoto@google.com +per-file ShortcutParser.java = omakoto@google.com +per-file ShortcutRequestPinProcessor.java = omakoto@google.com +per-file ShortcutService.java = omakoto@google.com +per-file ShortcutUser.java = omakoto@google.com + diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f4673a8fa7ba..bf872b7c48f6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -450,6 +450,9 @@ public class PackageManagerService extends IPackageManager.Stub private static final boolean ENABLE_FREE_CACHE_V2 = SystemProperties.getBoolean("fw.free_cache_v2", true); + private static final boolean PRECOMPILED_LAYOUT_ENABLED = + SystemProperties.getBoolean("view.precompiled_layout_enabled", false); + private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; @@ -9177,6 +9180,10 @@ public class PackageManagerService extends IPackageManager.Stub pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT; } + if (PRECOMPILED_LAYOUT_ENABLED) { + mArtManagerService.compileLayouts(pkg); + } + // checkProfiles is false to avoid merging profiles during boot which // might interfere with background compilation (b/28612421). // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will @@ -17739,6 +17746,13 @@ public class PackageManagerService extends IPackageManager.Stub && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0); if (performDexopt) { + // Compile the layout resources. + if (PRECOMPILED_LAYOUT_ENABLED) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts"); + mArtManagerService.compileLayouts(pkg); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); // Do not run PackageDexOptimizer through the local performDexOpt // method because `pkg` may not be in `mPackages` yet. @@ -24483,6 +24497,21 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); PackageManagerService.this.notifyPackageUseLocked(packageName, reason); } } + + /** + * Ask the package manager to compile layouts in the given package. + */ + @Override + public boolean compileLayouts(String packageName) { + PackageParser.Package pkg; + synchronized (mPackages) { + pkg = mPackages.get(packageName); + if (pkg == null) { + return false; + } + } + return mArtManagerService.compileLayouts(pkg); + } } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index e5b903024795..e1c13026507f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -41,6 +41,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageManagerInternal; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -1146,6 +1147,7 @@ class PackageManagerShellCommand extends ShellCommand { String checkProfilesRaw = null; boolean secondaryDex = false; String split = null; + boolean compileLayouts = false; String opt; while ((opt = getNextOption()) != null) { @@ -1165,6 +1167,9 @@ class PackageManagerShellCommand extends ShellCommand { case "-r": compilationReason = getNextArgRequired(); break; + case "--compile-layouts": + compileLayouts = true; + break; case "--check-prof": checkProfilesRaw = getNextArgRequired(); break; @@ -1196,14 +1201,16 @@ class PackageManagerShellCommand extends ShellCommand { } } - if (compilerFilter != null && compilationReason != null) { - pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " + - "at the same time"); - return 1; - } - if (compilerFilter == null && compilationReason == null) { - pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " + - "reason (\"-r\") at the same time"); + final boolean compilerFilterGiven = compilerFilter != null; + final boolean compilationReasonGiven = compilationReason != null; + // Make sure exactly one of -m, -r, or --compile-layouts is given. + if ((!compilerFilterGiven && !compilationReasonGiven && !compileLayouts) + || (!compilerFilterGiven && compilationReasonGiven && compileLayouts) + || (compilerFilterGiven && !compilationReasonGiven && compileLayouts) + || (compilerFilterGiven && compilationReasonGiven && !compileLayouts) + || (compilerFilterGiven && compilationReasonGiven && compileLayouts)) { + pw.println("Must specify exactly one of compilation filter (\"-m\"), compilation " + + "reason (\"-r\"), or compile layouts (\"--compile-layouts\")"); return 1; } @@ -1217,15 +1224,16 @@ class PackageManagerShellCommand extends ShellCommand { return 1; } - String targetCompilerFilter; - if (compilerFilter != null) { + String targetCompilerFilter = null; + if (compilerFilterGiven) { if (!DexFile.isValidCompilerFilter(compilerFilter)) { pw.println("Error: \"" + compilerFilter + "\" is not a valid compilation filter."); return 1; } targetCompilerFilter = compilerFilter; - } else { + } + if (compilationReasonGiven) { int reason = -1; for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals( @@ -1267,12 +1275,19 @@ class PackageManagerShellCommand extends ShellCommand { pw.flush(); } - boolean result = secondaryDex + boolean result = true; + if (compileLayouts) { + PackageManagerInternal internal = LocalServices.getService( + PackageManagerInternal.class); + result = internal.compileLayouts(packageName); + } else { + result = secondaryDex ? mInterface.performDexOptSecondary(packageName, targetCompilerFilter, forceCompilation) : mInterface.performDexOptMode(packageName, checkProfiles, targetCompilerFilter, forceCompilation, true /* bootComplete */, split); + } if (!result) { failedPackages.add(packageName); } @@ -2908,6 +2923,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); pw.println(" --secondary-dex: compile app secondary dex files"); pw.println(" --split SPLIT: compile only the given split name"); + pw.println(" --compile-layouts: compile layout resources for faster inflation"); pw.println(""); pw.println(" force-dex-opt PACKAGE"); pw.println(" Force immediate execution of dex opt for the given PACKAGE."); diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 1f05dc966555..863bfd5ea391 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -472,6 +472,33 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { } /** + * Compile layout resources in a given package. + */ + public boolean compileLayouts(PackageParser.Package pkg) { + try { + final String packageName = pkg.packageName; + final String apkPath = pkg.baseCodePath; + final ApplicationInfo appInfo = pkg.applicationInfo; + final String outDexFile = appInfo.dataDir + "/code_cache/compiled_view.dex"; + Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath + + ") to " + outDexFile); + long callingId = Binder.clearCallingIdentity(); + try { + synchronized (mInstallLock) { + return mInstaller.compileLayouts(apkPath, packageName, outDexFile, + appInfo.uid); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + catch (Throwable e) { + Log.e("PackageManager", "Failed to compile layouts", e); + return false; + } + } + + /** * Build the profiles names for all the package code paths (excluding resource only paths). * Return the map [code path -> profile name]. */ diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS new file mode 100644 index 000000000000..fcc1f6c10eac --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/OWNERS @@ -0,0 +1,4 @@ +agampe@google.com +calin@google.com +ngeoffray@google.com +sehr@google.com diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS index 88b97ea2cb49..01dc01efaca8 100644 --- a/services/core/java/com/android/server/pm/permission/OWNERS +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -1,4 +1,4 @@ -per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com +moltmann@google.com per-file DefaultPermissionGrantPolicy.java = hackbod@android.com per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java index 32513c1332bc..e99dd4f1cbae 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java @@ -21,9 +21,12 @@ import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteCursor; +import android.database.sqlite.SQLiteCursorDriver; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQuery; import android.net.NetworkUtils; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.Status; @@ -35,6 +38,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; /** * Encapsulating class for using the SQLite database backing the memory store. @@ -46,6 +50,9 @@ import java.util.List; */ public class IpMemoryStoreDatabase { private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName(); + // A pair of NetworkAttributes objects is group-close if the confidence that they are + // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse. + private static final float GROUPCLOSE_CONFIDENCE = 0.5f; /** * Contract class for the Network Attributes table. @@ -187,30 +194,35 @@ public class IpMemoryStoreDatabase { return addresses; } + @NonNull + private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) { + final ContentValues values = new ContentValues(); + if (null == attributes) return values; + if (null != attributes.assignedV4Address) { + values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, + NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); + } + if (null != attributes.groupHint) { + values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); + } + if (null != attributes.dnsAddresses) { + values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES, + encodeAddressList(attributes.dnsAddresses)); + } + if (null != attributes.mtu) { + values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu); + } + return values; + } + // Convert a NetworkAttributes object to content values to store them in a table compliant // with the contract defined in NetworkAttributesContract. @NonNull private static ContentValues toContentValues(@NonNull final String key, @Nullable final NetworkAttributes attributes, final long expiry) { - final ContentValues values = new ContentValues(); + final ContentValues values = toContentValues(attributes); values.put(NetworkAttributesContract.COLNAME_L2KEY, key); values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry); - if (null != attributes) { - if (null != attributes.assignedV4Address) { - values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, - NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); - } - if (null != attributes.groupHint) { - values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); - } - if (null != attributes.dnsAddresses) { - values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES, - encodeAddressList(attributes.dnsAddresses)); - } - if (null != attributes.mtu) { - values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu); - } - } return values; } @@ -228,6 +240,32 @@ public class IpMemoryStoreDatabase { return values; } + @Nullable + private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) { + // Make sure the data hasn't expired + final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L); + if (expiry < System.currentTimeMillis()) return null; + + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + final int assignedV4AddressInt = getInt(cursor, + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); + final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); + final byte[] dnsAddressesBlob = + getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); + final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); + if (0 != assignedV4AddressInt) { + builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); + } + builder.setGroupHint(groupHint); + if (null != dnsAddressesBlob) { + builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); + } + if (mtu >= 0) { + builder.setMtu(mtu); + } + return builder.build(); + } + private static final String[] EXPIRY_COLUMN = new String[] { NetworkAttributesContract.COLNAME_EXPIRYDATE }; @@ -313,32 +351,9 @@ public class IpMemoryStoreDatabase { // result here. 0 results means the key was not found. if (cursor.getCount() != 1) return null; cursor.moveToFirst(); - - // Make sure the data hasn't expired - final long expiry = cursor.getLong( - cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE)); - if (expiry < System.currentTimeMillis()) return null; - - final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); - final int assignedV4AddressInt = getInt(cursor, - NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); - final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); - final byte[] dnsAddressesBlob = - getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); - final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); + final NetworkAttributes attributes = readNetworkAttributesLine(cursor); cursor.close(); - - if (0 != assignedV4AddressInt) { - builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); - } - builder.setGroupHint(groupHint); - if (null != dnsAddressesBlob) { - builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); - } - if (mtu >= 0) { - builder.setMtu(mtu); - } - return builder.build(); + return attributes; } private static final String[] DATA_COLUMN = new String[] { @@ -365,17 +380,134 @@ public class IpMemoryStoreDatabase { return result; } + /** + * The following is a horrible hack that is necessary because the Android SQLite API does not + * have a way to query a binary blob. This, almost certainly, is an overlook. + * + * The Android SQLite API has two family of methods : one for query that returns data, and + * one for more general SQL statements that can execute any statement but may not return + * anything. All the query methods, however, take only String[] for the arguments. + * + * In principle it is simple to write a function that will encode the binary blob in the + * way SQLite expects it. However, because the API forces the argument to be coerced into a + * String, the SQLiteQuery object generated by the default query methods will bind all + * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types, + * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will + * be sanitized, changing the contents of the field, and the query will fail to match the + * blob. + * + * As far as I can tell, there are two possible ways around this problem. The first one + * is to put the data in the query string and eschew it being an argument. This would + * require doing the sanitizing by hand. The other is to call bindBlob directly on the + * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out + * sanitizing, but also will do the right thing if the underlying format ever changes. + * + * But none of the methods that take an SQLiteQuery object can return data ; this *must* + * be called with SQLiteDatabase#query. This object is not accessible from outside. + * However, there is a #query version that accepts a CursorFactory and this is pretty + * straightforward to implement as all the arguments are coming in and the SQLiteCursor + * class is public API. + * With this, it's possible to intercept the SQLiteQuery object, and assuming the args + * are available, to bind them directly and work around the API's oblivious coercion into + * Strings. + * + * This is really sad, but I don't see another way of having this work than this or the + * hand-rolled sanitizing, and this is the lesser evil. + */ + private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory { + @NonNull + private final ArrayList<Object> mArgs; + CustomCursorFactory(@NonNull final ArrayList<Object> args) { + mArgs = args; + } + @Override + public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery, + final String editTable, + final SQLiteQuery query) { + int index = 1; // bind is 1-indexed + for (final Object arg : mArgs) { + if (arg instanceof String) { + query.bindString(index++, (String) arg); + } else if (arg instanceof Long) { + query.bindLong(index++, (Long) arg); + } else if (arg instanceof Integer) { + query.bindLong(index++, Long.valueOf((Integer) arg)); + } else if (arg instanceof byte[]) { + query.bindBlob(index++, (byte[]) arg); + } else { + throw new IllegalStateException("Unsupported type CustomCursorFactory " + + arg.getClass().toString()); + } + } + return new SQLiteCursor(masterQuery, editTable, query); + } + } + + // Returns the l2key of the closest match, if and only if it matches + // closely enough (as determined by group-closeness). + @Nullable + static String findClosestAttributes(@NonNull final SQLiteDatabase db, + @NonNull final NetworkAttributes attr) { + if (attr.isEmpty()) return null; + final ContentValues values = toContentValues(attr); + + // Build the selection and args. To cut down on the number of lines to search, limit + // the search to those with at least one argument equals to the requested attributes. + // This works only because null attributes match only will not result in group-closeness. + final StringJoiner sj = new StringJoiner(" OR "); + final ArrayList<Object> args = new ArrayList<>(); + args.add(System.currentTimeMillis()); + for (final String field : values.keySet()) { + sj.add(field + " = ?"); + args.add(values.get(field)); + } + + final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND (" + + sj.toString() + ")"; + final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args), + false, // distinct + NetworkAttributesContract.TABLENAME, + null, // columns, null means everything + selection, // selection + null, // selectionArgs, horrendously passed to the cursor factory instead + null, // groupBy + null, // having + null, // orderBy + null); // limit + if (cursor.getCount() <= 0) return null; + cursor.moveToFirst(); + String bestKey = null; + float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this. + while (!cursor.isAfterLast()) { + final NetworkAttributes read = readNetworkAttributesLine(cursor); + final float confidence = read.getNetworkGroupSamenessConfidence(attr); + if (confidence > bestMatchConfidence) { + bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY); + bestMatchConfidence = confidence; + } + cursor.moveToNext(); + } + cursor.close(); + return bestKey; + } + // Helper methods - static String getString(final Cursor cursor, final String columnName) { + private static String getString(final Cursor cursor, final String columnName) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getString(columnIndex) : null; } - static byte[] getBlob(final Cursor cursor, final String columnName) { + private static byte[] getBlob(final Cursor cursor, final String columnName) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null; } - static int getInt(final Cursor cursor, final String columnName, final int defaultValue) { + private static int getInt(final Cursor cursor, final String columnName, + final int defaultValue) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue; } + private static long getLong(final Cursor cursor, final String columnName, + final long defaultValue) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue; + } } diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java index 8b521f415925..d43dc6a24260 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java @@ -250,9 +250,26 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * Through the listener, returns the L2 key if one matched, or null. */ @Override - public void findL2Key(@NonNull final NetworkAttributesParcelable attributes, - @NonNull final IOnL2KeyResponseListener listener) { - // TODO : implement this. + public void findL2Key(@Nullable final NetworkAttributesParcelable attributes, + @Nullable final IOnL2KeyResponseListener listener) { + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == attributes) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + if (null == mDb) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb, + new NetworkAttributes(attributes)); + listener.onL2KeyResponse(makeStatus(SUCCESS), key); + } catch (final RemoteException e) { + // Client at the other end died + } + }); } /** diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0569b912bbfb..3ecbd47cf12e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -803,7 +803,7 @@ public final class SystemServer { TimingsTraceLog traceLog = new TimingsTraceLog( SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER); traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD); - if (!Process.zygoteProcess.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) { + if (!Process.ZYGOTE_PROCESS.preloadDefault(Build.SUPPORTED_32_BIT_ABIS[0])) { Slog.e(TAG, "Unable to preload default resources"); } traceLog.traceEnd(); diff --git a/services/net/Android.bp b/services/net/Android.bp index 3b4d6a75591f..30c7de57b73e 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -3,18 +3,18 @@ java_library_static { srcs: ["java/**/*.java"], } -// TODO: move to networking module with DhcpClient and remove lib -java_library { - name: "dhcp-packet-lib", - srcs: [ - "java/android/net/dhcp/*Packet.java", - ] -} - filegroup { name: "services-networkstack-shared-srcs", srcs: [ - "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient + "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering + "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer "java/android/net/shared/*.java", + ], +} + +java_library { + name: "services-netlink-lib", + srcs: [ + "java/android/net/netlink/*.java", ] } diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 04ac9a301813..cddb91f65d0f 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * 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. @@ -16,1037 +16,14 @@ package android.net.dhcp; -import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; -import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; -import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; -import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; -import static android.net.dhcp.DhcpPacket.DHCP_MTU; -import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; -import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; -import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; -import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; -import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; -import static android.net.dhcp.DhcpPacket.INADDR_ANY; -import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; -import static android.net.util.SocketUtils.makePacketSocketAddress; -import static android.system.OsConstants.AF_INET; -import static android.system.OsConstants.AF_PACKET; -import static android.system.OsConstants.ETH_P_IP; -import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.SOCK_DGRAM; -import static android.system.OsConstants.SOCK_RAW; -import static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_BROADCAST; -import static android.system.OsConstants.SO_RCVBUF; -import static android.system.OsConstants.SO_REUSEADDR; - -import android.content.Context; -import android.net.DhcpResults; -import android.net.NetworkUtils; -import android.net.TrafficStats; -import android.net.ip.IpClient; -import android.net.metrics.DhcpClientEvent; -import android.net.metrics.DhcpErrorEvent; -import android.net.metrics.IpConnectivityLog; -import android.net.util.InterfaceParams; -import android.net.util.SocketUtils; -import android.os.Message; -import android.os.SystemClock; -import android.system.ErrnoException; -import android.system.Os; -import android.util.EventLog; -import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.util.HexDump; -import com.android.internal.util.MessageUtils; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.internal.util.WakeupMessage; - -import libcore.io.IoBridge; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.Inet4Address; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Random; - /** - * A DHCPv4 client. - * - * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android - * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine. - * - * TODO: - * - * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour). - * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not - * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a - * given SSID), it requests the last-leased IP address on the same interface, causing a delay if - * the server NAKs or a timeout if it doesn't. - * - * Known differences from current behaviour: - * - * - Does not request the "static routes" option. - * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now. - * - Requests the "broadcast" option, but does nothing with it. - * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0). - * - * @hide + * TODO: remove this class after migrating clients. */ -public class DhcpClient extends StateMachine { - - private static final String TAG = "DhcpClient"; - private static final boolean DBG = true; - private static final boolean STATE_DBG = false; - private static final boolean MSG_DBG = false; - private static final boolean PACKET_DBG = false; - - // Timers and timeouts. - private static final int SECONDS = 1000; - private static final int FIRST_TIMEOUT_MS = 2 * SECONDS; - private static final int MAX_TIMEOUT_MS = 128 * SECONDS; - - // This is not strictly needed, since the client is asynchronous and implements exponential - // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was - // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at - // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. - private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; - - // DhcpClient uses IpClient's handler. - private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; - - /* Commands from controller to start/stop DHCP */ - public static final int CMD_START_DHCP = PUBLIC_BASE + 1; - public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; +public class DhcpClient { + public static final int CMD_PRE_DHCP_ACTION = 1003; + public static final int CMD_POST_DHCP_ACTION = 1004; + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006; - /* Notification from DHCP state machine prior to DHCP discovery/renewal */ - public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3; - /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates - * success/failure */ - public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4; - /* Notification from DHCP state machine before quitting */ - public static final int CMD_ON_QUIT = PUBLIC_BASE + 5; - - /* Command from controller to indicate DHCP discovery/renewal can continue - * after pre DHCP action is complete */ - public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6; - - /* Command and event notification to/from IpManager requesting the setting - * (or clearing) of an IPv4 LinkAddress. - */ - public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7; - public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8; - public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9; - - /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ public static final int DHCP_SUCCESS = 1; public static final int DHCP_FAILURE = 2; - - // Internal messages. - private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100; - private static final int CMD_KICK = PRIVATE_BASE + 1; - private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; - private static final int CMD_TIMEOUT = PRIVATE_BASE + 3; - private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4; - private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5; - private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6; - - // For message logging. - private static final Class[] sMessageClasses = { DhcpClient.class }; - private static final SparseArray<String> sMessageNames = - MessageUtils.findMessageNames(sMessageClasses); - - // DHCP parameters that we request. - /* package */ static final byte[] REQUESTED_PARAMS = new byte[] { - DHCP_SUBNET_MASK, - DHCP_ROUTER, - DHCP_DNS_SERVER, - DHCP_DOMAIN_NAME, - DHCP_MTU, - DHCP_BROADCAST_ADDRESS, // TODO: currently ignored. - DHCP_LEASE_TIME, - DHCP_RENEWAL_TIME, - DHCP_REBINDING_TIME, - DHCP_VENDOR_INFO, - }; - - // DHCP flag that means "yes, we support unicast." - private static final boolean DO_UNICAST = false; - - // System services / libraries we use. - private final Context mContext; - private final Random mRandom; - private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); - - // Sockets. - // - We use a packet socket to receive, because servers send us packets bound for IP addresses - // which we have not yet configured, and the kernel protocol stack drops these. - // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can - // be off-link as well as on-link). - private FileDescriptor mPacketSock; - private FileDescriptor mUdpSock; - private ReceiveThread mReceiveThread; - - // State variables. - private final StateMachine mController; - private final WakeupMessage mKickAlarm; - private final WakeupMessage mTimeoutAlarm; - private final WakeupMessage mRenewAlarm; - private final WakeupMessage mRebindAlarm; - private final WakeupMessage mExpiryAlarm; - private final String mIfaceName; - - private boolean mRegisteredForPreDhcpNotification; - private InterfaceParams mIface; - // TODO: MacAddress-ify more of this class hierarchy. - private byte[] mHwAddr; - private SocketAddress mInterfaceBroadcastAddr; - private int mTransactionId; - private long mTransactionStartMillis; - private DhcpResults mDhcpLease; - private long mDhcpLeaseExpiry; - private DhcpResults mOffer; - - // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. - private long mLastInitEnterTime; - private long mLastBoundExitTime; - - // States. - private State mStoppedState = new StoppedState(); - private State mDhcpState = new DhcpState(); - private State mDhcpInitState = new DhcpInitState(); - private State mDhcpSelectingState = new DhcpSelectingState(); - private State mDhcpRequestingState = new DhcpRequestingState(); - private State mDhcpHaveLeaseState = new DhcpHaveLeaseState(); - private State mConfiguringInterfaceState = new ConfiguringInterfaceState(); - private State mDhcpBoundState = new DhcpBoundState(); - private State mDhcpRenewingState = new DhcpRenewingState(); - private State mDhcpRebindingState = new DhcpRebindingState(); - private State mDhcpInitRebootState = new DhcpInitRebootState(); - private State mDhcpRebootingState = new DhcpRebootingState(); - private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); - private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState); - - private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { - cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName; - return new WakeupMessage(mContext, getHandler(), cmdName, cmd); - } - - // TODO: Take an InterfaceParams instance instead of an interface name String. - private DhcpClient(Context context, StateMachine controller, String iface) { - super(TAG, controller.getHandler()); - - mContext = context; - mController = controller; - mIfaceName = iface; - - addState(mStoppedState); - addState(mDhcpState); - addState(mDhcpInitState, mDhcpState); - addState(mWaitBeforeStartState, mDhcpState); - addState(mDhcpSelectingState, mDhcpState); - addState(mDhcpRequestingState, mDhcpState); - addState(mDhcpHaveLeaseState, mDhcpState); - addState(mConfiguringInterfaceState, mDhcpHaveLeaseState); - addState(mDhcpBoundState, mDhcpHaveLeaseState); - addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState); - addState(mDhcpRenewingState, mDhcpHaveLeaseState); - addState(mDhcpRebindingState, mDhcpHaveLeaseState); - addState(mDhcpInitRebootState, mDhcpState); - addState(mDhcpRebootingState, mDhcpState); - - setInitialState(mStoppedState); - - mRandom = new Random(); - - // Used to schedule packet retransmissions. - mKickAlarm = makeWakeupMessage("KICK", CMD_KICK); - // Used to time out PacketRetransmittingStates. - mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT); - // Used to schedule DHCP reacquisition. - mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); - mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); - mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); - } - - public void registerForPreDhcpNotification() { - mRegisteredForPreDhcpNotification = true; - } - - public static DhcpClient makeDhcpClient( - Context context, StateMachine controller, InterfaceParams ifParams) { - DhcpClient client = new DhcpClient(context, controller, ifParams.name); - client.mIface = ifParams; - client.start(); - return client; - } - - private boolean initInterface() { - if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName); - if (mIface == null) { - Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName); - return false; - } - - mHwAddr = mIface.macAddr.toByteArray(); - mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST); - return true; - } - - private void startNewTransaction() { - mTransactionId = mRandom.nextInt(); - mTransactionStartMillis = SystemClock.elapsedRealtime(); - } - - private boolean initSockets() { - return initPacketSocket() && initUdpSocket(); - } - - private boolean initPacketSocket() { - try { - mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); - SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index); - Os.bind(mPacketSock, addr); - NetworkUtils.attachDhcpFilter(mPacketSock); - } catch(SocketException|ErrnoException e) { - Log.e(TAG, "Error creating packet socket", e); - return false; - } - return true; - } - - private boolean initUdpSocket() { - final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP); - try { - mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName); - Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); - Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); - Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); - Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); - } catch(SocketException|ErrnoException e) { - Log.e(TAG, "Error creating UDP socket", e); - return false; - } finally { - TrafficStats.setThreadStatsTag(oldTag); - } - return true; - } - - private boolean connectUdpSock(Inet4Address to) { - try { - Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER); - return true; - } catch (SocketException|ErrnoException e) { - Log.e(TAG, "Error connecting UDP socket", e); - return false; - } - } - - private static void closeQuietly(FileDescriptor fd) { - try { - IoBridge.closeAndSignalBlockedThreads(fd); - } catch (IOException ignored) {} - } - - private void closeSockets() { - closeQuietly(mUdpSock); - closeQuietly(mPacketSock); - } - - class ReceiveThread extends Thread { - - private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH]; - private volatile boolean mStopped = false; - - public void halt() { - mStopped = true; - closeSockets(); // Interrupts the read() call the thread is blocked in. - } - - @Override - public void run() { - if (DBG) Log.d(TAG, "Receive thread started"); - while (!mStopped) { - int length = 0; // Or compiler can't tell it's initialized if a parse error occurs. - try { - length = Os.read(mPacketSock, mPacket, 0, mPacket.length); - DhcpPacket packet = null; - packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2); - if (DBG) Log.d(TAG, "Received packet: " + packet); - sendMessage(CMD_RECEIVED_PACKET, packet); - } catch (IOException|ErrnoException e) { - if (!mStopped) { - Log.e(TAG, "Read error", e); - logError(DhcpErrorEvent.RECEIVE_ERROR); - } - } catch (DhcpPacket.ParseException e) { - Log.e(TAG, "Can't parse packet: " + e.getMessage()); - if (PACKET_DBG) { - Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length)); - } - if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) { - int snetTagId = 0x534e4554; - String bugId = "31850211"; - int uid = -1; - String data = DhcpPacket.ParseException.class.getName(); - EventLog.writeEvent(snetTagId, bugId, uid, data); - } - logError(e.errorCode); - } - } - if (DBG) Log.d(TAG, "Receive thread stopped"); - } - } - - private short getSecs() { - return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000); - } - - private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) { - try { - if (encap == DhcpPacket.ENCAP_L2) { - if (DBG) Log.d(TAG, "Broadcasting " + description); - Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); - } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) { - if (DBG) Log.d(TAG, "Broadcasting " + description); - // We only send L3-encapped broadcasts in DhcpRebindingState, - // where we have an IP address and an unconnected UDP socket. - // - // N.B.: We only need this codepath because DhcpRequestPacket - // hardcodes the source IP address to 0.0.0.0. We could reuse - // the packet socket if this ever changes. - Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER); - } else { - // It's safe to call getpeername here, because we only send unicast packets if we - // have an IP address, and we connect the UDP socket in DhcpBoundState#enter. - if (DBG) Log.d(TAG, String.format("Unicasting %s to %s", - description, Os.getpeername(mUdpSock))); - Os.write(mUdpSock, buf); - } - } catch(ErrnoException|IOException e) { - Log.e(TAG, "Can't send packet: ", e); - return false; - } - return true; - } - - private boolean sendDiscoverPacket() { - ByteBuffer packet = DhcpPacket.buildDiscoverPacket( - DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS); - return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); - } - - private boolean sendRequestPacket( - Inet4Address clientAddress, Inet4Address requestedAddress, - Inet4Address serverAddress, Inet4Address to) { - // TODO: should we use the transaction ID from the server? - final int encap = INADDR_ANY.equals(clientAddress) - ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP; - - ByteBuffer packet = DhcpPacket.buildRequestPacket( - encap, mTransactionId, getSecs(), clientAddress, - DO_UNICAST, mHwAddr, requestedAddress, - serverAddress, REQUESTED_PARAMS, null); - String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; - String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + - " request=" + requestedAddress.getHostAddress() + - " serverid=" + serverStr; - return transmitPacket(packet, description, encap, to); - } - - private void scheduleLeaseTimers() { - if (mDhcpLeaseExpiry == 0) { - Log.d(TAG, "Infinite lease, no timer scheduling needed"); - return; - } - - final long now = SystemClock.elapsedRealtime(); - - // TODO: consider getting the renew and rebind timers from T1 and T2. - // See also: - // https://tools.ietf.org/html/rfc2131#section-4.4.5 - // https://tools.ietf.org/html/rfc1533#section-9.9 - // https://tools.ietf.org/html/rfc1533#section-9.10 - final long remainingDelay = mDhcpLeaseExpiry - now; - final long renewDelay = remainingDelay / 2; - final long rebindDelay = remainingDelay * 7 / 8; - mRenewAlarm.schedule(now + renewDelay); - mRebindAlarm.schedule(now + rebindDelay); - mExpiryAlarm.schedule(now + remainingDelay); - Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s"); - Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s"); - Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s"); - } - - private void notifySuccess() { - mController.sendMessage( - CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); - } - - private void notifyFailure() { - mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null); - } - - private void acceptDhcpResults(DhcpResults results, String msg) { - mDhcpLease = results; - mOffer = null; - Log.d(TAG, msg + " lease: " + mDhcpLease); - notifySuccess(); - } - - private void clearDhcpState() { - mDhcpLease = null; - mDhcpLeaseExpiry = 0; - mOffer = null; - } - - /** - * Quit the DhcpStateMachine. - * - * @hide - */ - public void doQuit() { - Log.d(TAG, "doQuit"); - quit(); - } - - @Override - protected void onQuitting() { - Log.d(TAG, "onQuitting"); - mController.sendMessage(CMD_ON_QUIT); - } - - abstract class LoggingState extends State { - private long mEnterTimeMs; - - @Override - public void enter() { - if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); - mEnterTimeMs = SystemClock.elapsedRealtime(); - } - - @Override - public void exit() { - long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs; - logState(getName(), (int) durationMs); - } - - private String messageName(int what) { - return sMessageNames.get(what, Integer.toString(what)); - } - - private String messageToString(Message message) { - long now = SystemClock.uptimeMillis(); - return new StringBuilder(" ") - .append(message.getWhen() - now) - .append(messageName(message.what)) - .append(" ").append(message.arg1) - .append(" ").append(message.arg2) - .append(" ").append(message.obj) - .toString(); - } - - @Override - public boolean processMessage(Message message) { - if (MSG_DBG) { - Log.d(TAG, getName() + messageToString(message)); - } - return NOT_HANDLED; - } - - @Override - public String getName() { - // All DhcpClient's states are inner classes with a well defined name. - // Use getSimpleName() and avoid super's getName() creating new String instances. - return getClass().getSimpleName(); - } - } - - // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with - // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. - abstract class WaitBeforeOtherState extends LoggingState { - protected State mOtherState; - - @Override - public void enter() { - super.enter(); - mController.sendMessage(CMD_PRE_DHCP_ACTION); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_PRE_DHCP_ACTION_COMPLETE: - transitionTo(mOtherState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - class StoppedState extends State { - @Override - public boolean processMessage(Message message) { - switch (message.what) { - case CMD_START_DHCP: - if (mRegisteredForPreDhcpNotification) { - transitionTo(mWaitBeforeStartState); - } else { - transitionTo(mDhcpInitState); - } - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - class WaitBeforeStartState extends WaitBeforeOtherState { - public WaitBeforeStartState(State otherState) { - super(); - mOtherState = otherState; - } - } - - class WaitBeforeRenewalState extends WaitBeforeOtherState { - public WaitBeforeRenewalState(State otherState) { - super(); - mOtherState = otherState; - } - } - - class DhcpState extends State { - @Override - public void enter() { - clearDhcpState(); - if (initInterface() && initSockets()) { - mReceiveThread = new ReceiveThread(); - mReceiveThread.start(); - } else { - notifyFailure(); - transitionTo(mStoppedState); - } - } - - @Override - public void exit() { - if (mReceiveThread != null) { - mReceiveThread.halt(); // Also closes sockets. - mReceiveThread = null; - } - clearDhcpState(); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_STOP_DHCP: - transitionTo(mStoppedState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - public boolean isValidPacket(DhcpPacket packet) { - // TODO: check checksum. - int xid = packet.getTransactionId(); - if (xid != mTransactionId) { - Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId); - return false; - } - if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { - Log.d(TAG, "MAC addr mismatch: got " + - HexDump.toHexString(packet.getClientMac()) + ", expected " + - HexDump.toHexString(packet.getClientMac())); - return false; - } - return true; - } - - public void setDhcpLeaseExpiry(DhcpPacket packet) { - long leaseTimeMillis = packet.getLeaseTimeMillis(); - mDhcpLeaseExpiry = - (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0; - } - - /** - * Retransmits packets using jittered exponential backoff with an optional timeout. Packet - * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass - * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout - * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the - * state. - * - * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a - * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET - * sent by the receive thread. They may also set mTimeout and implement timeout. - */ - abstract class PacketRetransmittingState extends LoggingState { - - private int mTimer; - protected int mTimeout = 0; - - @Override - public void enter() { - super.enter(); - initTimer(); - maybeInitTimeout(); - sendMessage(CMD_KICK); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_KICK: - sendPacket(); - scheduleKick(); - return HANDLED; - case CMD_RECEIVED_PACKET: - receivePacket((DhcpPacket) message.obj); - return HANDLED; - case CMD_TIMEOUT: - timeout(); - return HANDLED; - default: - return NOT_HANDLED; - } - } - - @Override - public void exit() { - super.exit(); - mKickAlarm.cancel(); - mTimeoutAlarm.cancel(); - } - - abstract protected boolean sendPacket(); - abstract protected void receivePacket(DhcpPacket packet); - protected void timeout() {} - - protected void initTimer() { - mTimer = FIRST_TIMEOUT_MS; - } - - protected int jitterTimer(int baseTimer) { - int maxJitter = baseTimer / 10; - int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; - return baseTimer + jitter; - } - - protected void scheduleKick() { - long now = SystemClock.elapsedRealtime(); - long timeout = jitterTimer(mTimer); - long alarmTime = now + timeout; - mKickAlarm.schedule(alarmTime); - mTimer *= 2; - if (mTimer > MAX_TIMEOUT_MS) { - mTimer = MAX_TIMEOUT_MS; - } - } - - protected void maybeInitTimeout() { - if (mTimeout > 0) { - long alarmTime = SystemClock.elapsedRealtime() + mTimeout; - mTimeoutAlarm.schedule(alarmTime); - } - } - } - - class DhcpInitState extends PacketRetransmittingState { - public DhcpInitState() { - super(); - } - - @Override - public void enter() { - super.enter(); - startNewTransaction(); - mLastInitEnterTime = SystemClock.elapsedRealtime(); - } - - protected boolean sendPacket() { - return sendDiscoverPacket(); - } - - protected void receivePacket(DhcpPacket packet) { - if (!isValidPacket(packet)) return; - if (!(packet instanceof DhcpOfferPacket)) return; - mOffer = packet.toDhcpResults(); - if (mOffer != null) { - Log.d(TAG, "Got pending lease: " + mOffer); - transitionTo(mDhcpRequestingState); - } - } - } - - // Not implemented. We request the first offer we receive. - class DhcpSelectingState extends LoggingState { - } - - class DhcpRequestingState extends PacketRetransmittingState { - public DhcpRequestingState() { - mTimeout = DHCP_TIMEOUT_MS / 2; - } - - protected boolean sendPacket() { - return sendRequestPacket( - INADDR_ANY, // ciaddr - (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP - (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER - INADDR_BROADCAST); // packet destination address - } - - protected void receivePacket(DhcpPacket packet) { - if (!isValidPacket(packet)) return; - if ((packet instanceof DhcpAckPacket)) { - DhcpResults results = packet.toDhcpResults(); - if (results != null) { - setDhcpLeaseExpiry(packet); - acceptDhcpResults(results, "Confirmed"); - transitionTo(mConfiguringInterfaceState); - } - } else if (packet instanceof DhcpNakPacket) { - // TODO: Wait a while before returning into INIT state. - Log.d(TAG, "Received NAK, returning to INIT"); - mOffer = null; - transitionTo(mDhcpInitState); - } - } - - @Override - protected void timeout() { - // After sending REQUESTs unsuccessfully for a while, go back to init. - transitionTo(mDhcpInitState); - } - } - - class DhcpHaveLeaseState extends State { - @Override - public boolean processMessage(Message message) { - switch (message.what) { - case CMD_EXPIRE_DHCP: - Log.d(TAG, "Lease expired!"); - notifyFailure(); - transitionTo(mDhcpInitState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - - @Override - public void exit() { - // Clear any extant alarms. - mRenewAlarm.cancel(); - mRebindAlarm.cancel(); - mExpiryAlarm.cancel(); - clearDhcpState(); - // Tell IpManager to clear the IPv4 address. There is no need to - // wait for confirmation since any subsequent packets are sent from - // INADDR_ANY anyway (DISCOVER, REQUEST). - mController.sendMessage(CMD_CLEAR_LINKADDRESS); - } - } - - class ConfiguringInterfaceState extends LoggingState { - @Override - public void enter() { - super.enter(); - mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case EVENT_LINKADDRESS_CONFIGURED: - transitionTo(mDhcpBoundState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - class DhcpBoundState extends LoggingState { - @Override - public void enter() { - super.enter(); - if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) { - // There's likely no point in going into DhcpInitState here, we'll probably - // just repeat the transaction, get the same IP address as before, and fail. - // - // NOTE: It is observed that connectUdpSock() basically never fails, due to - // SO_BINDTODEVICE. Examining the local socket address shows it will happily - // return an IPv4 address from another interface, or even return "0.0.0.0". - // - // TODO: Consider deleting this check, following testing on several kernels. - notifyFailure(); - transitionTo(mStoppedState); - } - - scheduleLeaseTimers(); - logTimeToBoundState(); - } - - @Override - public void exit() { - super.exit(); - mLastBoundExitTime = SystemClock.elapsedRealtime(); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_RENEW_DHCP: - if (mRegisteredForPreDhcpNotification) { - transitionTo(mWaitBeforeRenewalState); - } else { - transitionTo(mDhcpRenewingState); - } - return HANDLED; - default: - return NOT_HANDLED; - } - } - - private void logTimeToBoundState() { - long now = SystemClock.elapsedRealtime(); - if (mLastBoundExitTime > mLastInitEnterTime) { - logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime)); - } else { - logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime)); - } - } - } - - abstract class DhcpReacquiringState extends PacketRetransmittingState { - protected String mLeaseMsg; - - @Override - public void enter() { - super.enter(); - startNewTransaction(); - } - - abstract protected Inet4Address packetDestination(); - - protected boolean sendPacket() { - return sendRequestPacket( - (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr - INADDR_ANY, // DHCP_REQUESTED_IP - null, // DHCP_SERVER_IDENTIFIER - packetDestination()); // packet destination address - } - - protected void receivePacket(DhcpPacket packet) { - if (!isValidPacket(packet)) return; - if ((packet instanceof DhcpAckPacket)) { - final DhcpResults results = packet.toDhcpResults(); - if (results != null) { - if (!mDhcpLease.ipAddress.equals(results.ipAddress)) { - Log.d(TAG, "Renewed lease not for our current IP address!"); - notifyFailure(); - transitionTo(mDhcpInitState); - } - setDhcpLeaseExpiry(packet); - // Updating our notion of DhcpResults here only causes the - // DNS servers and routes to be updated in LinkProperties - // in IpManager and by any overridden relevant handlers of - // the registered IpManager.Callback. IP address changes - // are not supported here. - acceptDhcpResults(results, mLeaseMsg); - transitionTo(mDhcpBoundState); - } - } else if (packet instanceof DhcpNakPacket) { - Log.d(TAG, "Received NAK, returning to INIT"); - notifyFailure(); - transitionTo(mDhcpInitState); - } - } - } - - class DhcpRenewingState extends DhcpReacquiringState { - public DhcpRenewingState() { - mLeaseMsg = "Renewed"; - } - - @Override - public boolean processMessage(Message message) { - if (super.processMessage(message) == HANDLED) { - return HANDLED; - } - - switch (message.what) { - case CMD_REBIND_DHCP: - transitionTo(mDhcpRebindingState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - - @Override - protected Inet4Address packetDestination() { - // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but... - // http://b/25343517 . Try to make things work anyway by using broadcast renews. - return (mDhcpLease.serverAddress != null) ? - mDhcpLease.serverAddress : INADDR_BROADCAST; - } - } - - class DhcpRebindingState extends DhcpReacquiringState { - public DhcpRebindingState() { - mLeaseMsg = "Rebound"; - } - - @Override - public void enter() { - super.enter(); - - // We need to broadcast and possibly reconnect the socket to a - // completely different server. - closeQuietly(mUdpSock); - if (!initUdpSocket()) { - Log.e(TAG, "Failed to recreate UDP socket"); - transitionTo(mDhcpInitState); - } - } - - @Override - protected Inet4Address packetDestination() { - return INADDR_BROADCAST; - } - } - - class DhcpInitRebootState extends LoggingState { - } - - class DhcpRebootingState extends LoggingState { - } - - private void logError(int errorCode) { - mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode)); - } - - private void logState(String name, int durationMs) { - final DhcpClientEvent event = new DhcpClientEvent.Builder() - .setMsg(name) - .setDurationMs(durationMs) - .build(); - mMetricsLog.log(mIfaceName, event); - } } diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 233b86f5f8b2..a61c2efd64da 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -16,225 +16,209 @@ package android.net.ip; -import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; import android.content.Context; -import android.net.ConnectivityManager; -import android.net.DhcpResults; -import android.net.INetd; -import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; -import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; -import android.net.ProxyInfoParcelable; -import android.net.RouteInfo; import android.net.StaticIpConfiguration; import android.net.apf.ApfCapabilities; -import android.net.apf.ApfFilter; -import android.net.dhcp.DhcpClient; -import android.net.metrics.IpConnectivityLog; -import android.net.metrics.IpManagerEvent; -import android.net.shared.InitialConfiguration; -import android.net.util.InterfaceParams; -import android.net.util.NetdService; -import android.net.util.SharedLog; import android.os.ConditionVariable; import android.os.INetworkManagementService; -import android.os.Message; import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.LocalLog; import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.IState; -import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.MessageUtils; -import com.android.internal.util.Preconditions; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.internal.util.WakeupMessage; -import com.android.server.net.NetlinkTracker; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.net.InetAddress; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.function.Predicate; -import java.util.stream.Collectors; - /** - * IpClient - * - * This class provides the interface to IP-layer provisioning and maintenance - * functionality that can be used by transport layers like Wi-Fi, Ethernet, - * et cetera. - * - * [ Lifetime ] - * IpClient is designed to be instantiated as soon as the interface name is - * known and can be as long-lived as the class containing it (i.e. declaring - * it "private final" is okay). - * + * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated. * @hide */ -public class IpClient extends StateMachine { - private static final boolean DBG = false; +public class IpClient { + private static final String TAG = IpClient.class.getSimpleName(); + private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000; - // For message logging. - private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class }; - private static final SparseArray<String> sWhatToString = - MessageUtils.findMessageNames(sMessageClasses); - // Two static concurrent hashmaps of interface name to logging classes. - // One holds StateMachine logs and the other connectivity packet logs. - private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>(); - - // If |args| is non-empty, assume it's a list of interface names for which - // we should print IpClient logs (filter out all others). - public static void dumpAllLogs(PrintWriter writer, String[] args) { - for (String ifname : sSmLogs.keySet()) { - if (!ArrayUtils.isEmpty(args) && !ArrayUtils.contains(args, ifname)) continue; - - writer.println(String.format("--- BEGIN %s ---", ifname)); - - final SharedLog smLog = sSmLogs.get(ifname); - if (smLog != null) { - writer.println("State machine log:"); - smLog.dump(null, writer, null); - } - - writer.println(""); + public static final String DUMP_ARG = "ipclient"; - final LocalLog pktLog = sPktLogs.get(ifname); - if (pktLog != null) { - writer.println("Connectivity packet log:"); - pktLog.readOnlyLocalLog().dump(null, writer, null); - } + private final ConditionVariable mIpClientCv; + private final ConditionVariable mShutdownCv; - writer.println(String.format("--- END %s ---", ifname)); - } - } + private volatile IIpClient mIpClient; /** - * TODO: remove after migrating clients to use IpClientCallbacks directly * @see IpClientCallbacks */ public static class Callback extends IpClientCallbacks {} /** - * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback - * @see IpClientUtil.WaitForProvisioningCallbacks + * IpClient callback that allows clients to block until provisioning is complete. */ - public static class WaitForProvisioningCallback - extends IpClientUtil.WaitForProvisioningCallbacks {} - - // Use a wrapper class to log in order to ensure complete and detailed - // logging. This method is lighter weight than annotations/reflection - // and has the following benefits: - // - // - No invoked method can be forgotten. - // Any new method added to IpClient.Callback must be overridden - // here or it will never be called. - // - // - No invoking call site can be forgotten. - // Centralized logging in this way means call sites don't need to - // remember to log, and therefore no call site can be forgotten. - // - // - No variation in log format among call sites. - // Encourages logging of any available arguments, and all call sites - // are necessarily logged identically. - // - // NOTE: Log first because passed objects may or may not be thread-safe and - // once passed on to the callback they may be modified by another thread. - // - // TODO: Find an lighter weight approach. - private class LoggingCallbackWrapper extends IpClientCallbacks { - private static final String PREFIX = "INVOKE "; - private final IpClientCallbacks mCallback; - - LoggingCallbackWrapper(IpClientCallbacks callback) { - mCallback = (callback != null) ? callback : new IpClientCallbacks(); - } + public static class WaitForProvisioningCallback extends Callback { + private final ConditionVariable mCV = new ConditionVariable(); + private LinkProperties mCallbackLinkProperties; - private void log(String msg) { - mLog.log(PREFIX + msg); + /** + * Block until either {@link #onProvisioningSuccess(LinkProperties)} or + * {@link #onProvisioningFailure(LinkProperties)} is called. + */ + public LinkProperties waitForProvisioning() { + mCV.block(); + return mCallbackLinkProperties; } @Override - public void onPreDhcpAction() { - log("onPreDhcpAction()"); - mCallback.onPreDhcpAction(); - } - @Override - public void onPostDhcpAction() { - log("onPostDhcpAction()"); - mCallback.onPostDhcpAction(); - } - @Override - public void onNewDhcpResults(DhcpResults dhcpResults) { - log("onNewDhcpResults({" + dhcpResults + "})"); - mCallback.onNewDhcpResults(dhcpResults); - } - @Override public void onProvisioningSuccess(LinkProperties newLp) { - log("onProvisioningSuccess({" + newLp + "})"); - mCallback.onProvisioningSuccess(newLp); + mCallbackLinkProperties = newLp; + mCV.open(); } + @Override public void onProvisioningFailure(LinkProperties newLp) { - log("onProvisioningFailure({" + newLp + "})"); - mCallback.onProvisioningFailure(newLp); + mCallbackLinkProperties = null; + mCV.open(); } - @Override - public void onLinkPropertiesChange(LinkProperties newLp) { - log("onLinkPropertiesChange({" + newLp + "})"); - mCallback.onLinkPropertiesChange(newLp); + } + + private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy { + /** + * Create a new IpClientCallbacksProxy. + */ + CallbackImpl(IpClientCallbacks cb) { + super(cb); } + @Override - public void onReachabilityLost(String logMsg) { - log("onReachabilityLost(" + logMsg + ")"); - mCallback.onReachabilityLost(logMsg); + public void onIpClientCreated(IIpClient ipClient) { + mIpClient = ipClient; + mIpClientCv.open(); + super.onIpClientCreated(ipClient); } + @Override public void onQuit() { - log("onQuit()"); - mCallback.onQuit(); - } - @Override - public void installPacketFilter(byte[] filter) { - log("installPacketFilter(byte[" + filter.length + "])"); - mCallback.installPacketFilter(filter); - } - @Override - public void startReadPacketFilter() { - log("startReadPacketFilter()"); - mCallback.startReadPacketFilter(); - } - @Override - public void setFallbackMulticastFilter(boolean enabled) { - log("setFallbackMulticastFilter(" + enabled + ")"); - mCallback.setFallbackMulticastFilter(enabled); + mShutdownCv.open(); + super.onQuit(); } - @Override - public void setNeighborDiscoveryOffload(boolean enable) { - log("setNeighborDiscoveryOffload(" + enable + ")"); - mCallback.setNeighborDiscoveryOffload(enable); + } + + /** + * Create a new IpClient. + */ + public IpClient(Context context, String iface, Callback callback) { + mIpClientCv = new ConditionVariable(false); + mShutdownCv = new ConditionVariable(false); + + IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback)); + } + + /** + * @see IpClient#IpClient(Context, String, IpClient.Callback) + */ + public IpClient(Context context, String iface, Callback callback, + INetworkManagementService nms) { + this(context, iface, callback); + } + + private interface IpClientAction { + void useIpClient(IIpClient ipClient) throws RemoteException; + } + + private void doWithIpClient(IpClientAction action) { + mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); + try { + action.useIpClient(mIpClient); + } catch (RemoteException e) { + Log.e(TAG, "Error communicating with IpClient", e); } } /** + * Notify IpClient that PreDhcpAction is completed. + */ + public void completedPreDhcpAction() { + doWithIpClient(c -> c.completedPreDhcpAction()); + } + + /** + * Confirm the provisioning configuration. + */ + public void confirmConfiguration() { + doWithIpClient(c -> c.confirmConfiguration()); + } + + /** + * Notify IpClient that packet filter read is complete. + */ + public void readPacketFilterComplete(byte[] data) { + doWithIpClient(c -> c.readPacketFilterComplete(data)); + } + + /** + * Shutdown the IpClient altogether. + */ + public void shutdown() { + doWithIpClient(c -> c.shutdown()); + } + + /** + * Start the IpClient provisioning. + */ + public void startProvisioning(ProvisioningConfiguration config) { + doWithIpClient(c -> c.startProvisioning(config.toStableParcelable())); + } + + /** + * Stop the IpClient. + */ + public void stop() { + doWithIpClient(c -> c.stop()); + } + + /** + * Set the IpClient TCP buffer sizes. + */ + public void setTcpBufferSizes(String tcpBufferSizes) { + doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes)); + } + + /** + * Set the IpClient HTTP proxy. + */ + public void setHttpProxy(ProxyInfo proxyInfo) { + doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo))); + } + + /** + * Set the IpClient multicast filter. + */ + public void setMulticastFilter(boolean enabled) { + doWithIpClient(c -> c.setMulticastFilter(enabled)); + } + + /** + * Dump IpClient logs. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args)); + } + + /** + * Block until IpClient shutdown. + */ + public void awaitShutdown() { + mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); + } + + /** + * Create a new ProvisioningConfiguration. + */ + public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { + return new ProvisioningConfiguration.Builder(); + } + + /** * TODO: remove after migrating clients to use the shared configuration class directly. * @see android.net.shared.ProvisioningConfiguration */ @@ -333,1430 +317,4 @@ public class IpClient extends StateMachine { } } } - - public static final String DUMP_ARG = "ipclient"; - public static final String DUMP_ARG_CONFIRM = "confirm"; - - private static final int CMD_TERMINATE_AFTER_STOP = 1; - private static final int CMD_STOP = 2; - private static final int CMD_START = 3; - private static final int CMD_CONFIRM = 4; - private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; - // Triggered by NetlinkTracker to communicate netlink events. - private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; - private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; - private static final int CMD_UPDATE_HTTP_PROXY = 8; - private static final int CMD_SET_MULTICAST_FILTER = 9; - private static final int EVENT_PROVISIONING_TIMEOUT = 10; - private static final int EVENT_DHCPACTION_TIMEOUT = 11; - private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; - - // Internal commands to use instead of trying to call transitionTo() inside - // a given State's enter() method. Calling transitionTo() from enter/exit - // encounters a Log.wtf() that can cause trouble on eng builds. - private static final int CMD_JUMP_STARTED_TO_RUNNING = 100; - private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101; - private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102; - - // IpClient shares a handler with DhcpClient: commands must not overlap - public static final int DHCPCLIENT_CMD_BASE = 1000; - - private static final int MAX_LOG_RECORDS = 500; - private static final int MAX_PACKET_RECORDS = 100; - - private static final boolean NO_CALLBACKS = false; - private static final boolean SEND_CALLBACKS = true; - - // This must match the interface prefix in clatd.c. - // TODO: Revert this hack once IpClient and Nat464Xlat work in concert. - private static final String CLAT_PREFIX = "v4-"; - - private static final int IMMEDIATE_FAILURE_DURATION = 0; - - private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1; - private static final int PROV_CHANGE_LOST_PROVISIONING = 2; - private static final int PROV_CHANGE_GAINED_PROVISIONING = 3; - private static final int PROV_CHANGE_STILL_PROVISIONED = 4; - - private final State mStoppedState = new StoppedState(); - private final State mStoppingState = new StoppingState(); - private final State mStartedState = new StartedState(); - private final State mRunningState = new RunningState(); - - private final String mTag; - private final Context mContext; - private final String mInterfaceName; - private final String mClatInterfaceName; - @VisibleForTesting - protected final IpClientCallbacks mCallback; - private final Dependencies mDependencies; - private final CountDownLatch mShutdownLatch; - private final ConnectivityManager mCm; - private final INetworkManagementService mNwService; - private final NetlinkTracker mNetlinkTracker; - private final WakeupMessage mProvisioningTimeoutAlarm; - private final WakeupMessage mDhcpActionTimeoutAlarm; - private final SharedLog mLog; - private final LocalLog mConnectivityPacketLog; - private final MessageHandlingLogger mMsgStateLogger; - private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); - private final InterfaceController mInterfaceCtrl; - - private InterfaceParams mInterfaceParams; - - /** - * Non-final member variables accessed only from within our StateMachine. - */ - private LinkProperties mLinkProperties; - private android.net.shared.ProvisioningConfiguration mConfiguration; - private IpReachabilityMonitor mIpReachabilityMonitor; - private DhcpClient mDhcpClient; - private DhcpResults mDhcpResults; - private String mTcpBufferSizes; - private ProxyInfo mHttpProxy; - private ApfFilter mApfFilter; - private boolean mMulticastFiltering; - private long mStartTimeMillis; - - /** - * Reading the snapshot is an asynchronous operation initiated by invoking - * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an - * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable - * signals when a new snapshot is ready. - */ - private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable(); - - public static class Dependencies { - public INetworkManagementService getNMS() { - return INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); - } - - public INetd getNetd() { - return NetdService.getInstance(); - } - - /** - * Get interface parameters for the specified interface. - */ - public InterfaceParams getInterfaceParams(String ifname) { - return InterfaceParams.getByName(ifname); - } - } - - public IpClient(Context context, String ifName, IpClientCallbacks callback) { - this(context, ifName, callback, new Dependencies()); - } - - /** - * An expanded constructor, useful for dependency injection. - * TODO: migrate all test users to mock IpClient directly and remove this ctor. - */ - public IpClient(Context context, String ifName, IpClientCallbacks callback, - INetworkManagementService nwService) { - this(context, ifName, callback, new Dependencies() { - @Override - public INetworkManagementService getNMS() { - return nwService; - } - }); - } - - @VisibleForTesting - IpClient(Context context, String ifName, IpClientCallbacks callback, Dependencies deps) { - super(IpClient.class.getSimpleName() + "." + ifName); - Preconditions.checkNotNull(ifName); - Preconditions.checkNotNull(callback); - - mTag = getName(); - - mContext = context; - mInterfaceName = ifName; - mClatInterfaceName = CLAT_PREFIX + ifName; - mCallback = new LoggingCallbackWrapper(callback); - mDependencies = deps; - mShutdownLatch = new CountDownLatch(1); - mCm = mContext.getSystemService(ConnectivityManager.class); - mNwService = deps.getNMS(); - - sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag)); - mLog = sSmLogs.get(mInterfaceName); - sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS)); - mConnectivityPacketLog = sPktLogs.get(mInterfaceName); - mMsgStateLogger = new MessageHandlingLogger(); - - // TODO: Consider creating, constructing, and passing in some kind of - // InterfaceController.Dependencies class. - mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog); - - mNetlinkTracker = new NetlinkTracker( - mInterfaceName, - new NetlinkTracker.Callback() { - @Override - public void update() { - sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); - } - }) { - @Override - public void interfaceAdded(String iface) { - super.interfaceAdded(iface); - if (mClatInterfaceName.equals(iface)) { - mCallback.setNeighborDiscoveryOffload(false); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceAdded(" + iface + ")"; - logMsg(msg); - } - - @Override - public void interfaceRemoved(String iface) { - super.interfaceRemoved(iface); - // TODO: Also observe mInterfaceName going down and take some - // kind of appropriate action. - if (mClatInterfaceName.equals(iface)) { - // TODO: consider sending a message to the IpClient main - // StateMachine thread, in case "NDO enabled" state becomes - // tied to more things that 464xlat operation. - mCallback.setNeighborDiscoveryOffload(true); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceRemoved(" + iface + ")"; - logMsg(msg); - } - - private void logMsg(String msg) { - Log.d(mTag, msg); - getHandler().post(() -> mLog.log("OBSERVED " + msg)); - } - }; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - - mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); - mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); - - // Anything the StateMachine may access must have been instantiated - // before this point. - configureAndStartStateMachine(); - - // Anything that may send messages to the StateMachine must only be - // configured to do so after the StateMachine has started (above). - startStateMachineUpdaters(); - } - - /** - * Make a IIpClient connector to communicate with this IpClient. - */ - public IIpClient makeConnector() { - return new IpClientConnector(); - } - - class IpClientConnector extends IIpClient.Stub { - @Override - public void completedPreDhcpAction() { - IpClient.this.completedPreDhcpAction(); - } - @Override - public void confirmConfiguration() { - IpClient.this.confirmConfiguration(); - } - @Override - public void readPacketFilterComplete(byte[] data) { - IpClient.this.readPacketFilterComplete(data); - } - @Override - public void shutdown() { - IpClient.this.shutdown(); - } - @Override - public void startProvisioning(ProvisioningConfigurationParcelable req) { - IpClient.this.startProvisioning( - android.net.shared.ProvisioningConfiguration.fromStableParcelable(req)); - } - @Override - public void stop() { - IpClient.this.stop(); - } - @Override - public void setTcpBufferSizes(String tcpBufferSizes) { - IpClient.this.setTcpBufferSizes(tcpBufferSizes); - } - @Override - public void setHttpProxy(ProxyInfoParcelable proxyInfo) { - IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo)); - } - @Override - public void setMulticastFilter(boolean enabled) { - IpClient.this.setMulticastFilter(enabled); - } - // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys - public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) { - IpClient.this.dump(fd, pw, args); - } - } - - private void configureAndStartStateMachine() { - // CHECKSTYLE:OFF IndentationCheck - addState(mStoppedState); - addState(mStartedState); - addState(mRunningState, mStartedState); - addState(mStoppingState); - // CHECKSTYLE:ON IndentationCheck - - setInitialState(mStoppedState); - - super.start(); - } - - private void startStateMachineUpdaters() { - try { - mNwService.registerObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't register NetlinkTracker: %s", e); - } - } - - private void stopStateMachineUpdaters() { - try { - mNwService.unregisterObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't unregister NetlinkTracker: %s", e); - } - } - - @Override - protected void onQuitting() { - mCallback.onQuit(); - mShutdownLatch.countDown(); - } - - /** - * Shut down this IpClient instance altogether. - */ - public void shutdown() { - stop(); - sendMessage(CMD_TERMINATE_AFTER_STOP); - } - - // In order to avoid deadlock, this method MUST NOT be called on the - // IpClient instance's thread. This prohibition includes code executed by - // when methods on the passed-in IpClient.Callback instance are called. - public void awaitShutdown() { - try { - mShutdownLatch.await(); - } catch (InterruptedException e) { - mLog.e("Interrupted while awaiting shutdown: " + e); - } - } - - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); - } - - /** - * Start provisioning with the provided parameters. - */ - public void startProvisioning(android.net.shared.ProvisioningConfiguration req) { - if (!req.isValid()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - return; - } - - mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName); - if (mInterfaceParams == null) { - logError("Failed to find InterfaceParams for " + mInterfaceName); - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND); - return; - } - - mCallback.setNeighborDiscoveryOffload(true); - sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); - } - - // TODO: Delete this. - public void startProvisioning(StaticIpConfiguration staticIpConfig) { - startProvisioning(buildProvisioningConfiguration() - .withStaticConfiguration(staticIpConfig) - .build()); - } - - public void startProvisioning() { - startProvisioning(new android.net.shared.ProvisioningConfiguration()); - } - - /** - * Stop this IpClient. - * - * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}. - */ - public void stop() { - sendMessage(CMD_STOP); - } - - /** - * Confirm the provisioning configuration. - */ - public void confirmConfiguration() { - sendMessage(CMD_CONFIRM); - } - - /** - * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be - * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to - * proceed. - */ - public void completedPreDhcpAction() { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - - /** - * Indicate that packet filter read is complete. - */ - public void readPacketFilterComplete(byte[] data) { - sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data); - } - - /** - * Set the TCP buffer sizes to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setTcpBufferSizes(String tcpBufferSizes) { - sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); - } - - /** - * Set the HTTP Proxy configuration to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setHttpProxy(ProxyInfo proxyInfo) { - sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); - } - - /** - * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, - * if not, Callback.setFallbackMulticastFilter() is called. - */ - public void setMulticastFilter(boolean enabled) { - sendMessage(CMD_SET_MULTICAST_FILTER, enabled); - } - - /** - * Dump logs of this IpClient. - */ - public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { - // Execute confirmConfiguration() and take no further action. - confirmConfiguration(); - return; - } - - // Thread-unsafe access to mApfFilter but just used for debugging. - final ApfFilter apfFilter = mApfFilter; - final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration; - final ApfCapabilities apfCapabilities = (provisioningConfig != null) - ? provisioningConfig.mApfCapabilities : null; - - IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println(mTag + " APF dump:"); - pw.increaseIndent(); - if (apfFilter != null) { - if (apfCapabilities.hasDataAccess()) { - // Request a new snapshot, then wait for it. - mApfDataSnapshotComplete.close(); - mCallback.startReadPacketFilter(); - if (!mApfDataSnapshotComplete.block(1000)) { - pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT"); - } - } - apfFilter.dump(pw); - - } else { - pw.print("No active ApfFilter; "); - if (provisioningConfig == null) { - pw.println("IpClient not yet started."); - } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { - pw.println("Hardware does not support APF."); - } else { - pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); - } - } - pw.decreaseIndent(); - pw.println(); - pw.println(mTag + " current ProvisioningConfiguration:"); - pw.increaseIndent(); - pw.println(Objects.toString(provisioningConfig, "N/A")); - pw.decreaseIndent(); - - final IpReachabilityMonitor iprm = mIpReachabilityMonitor; - if (iprm != null) { - pw.println(); - pw.println(mTag + " current IpReachabilityMonitor state:"); - pw.increaseIndent(); - iprm.dump(pw); - pw.decreaseIndent(); - } - - pw.println(); - pw.println(mTag + " StateMachine dump:"); - pw.increaseIndent(); - mLog.dump(fd, pw, args); - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " connectivity packet log:"); - pw.println(); - pw.println("Debug with python and scapy via:"); - pw.println("shell$ python"); - pw.println(">>> from scapy import all as scapy"); - pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); - pw.println(); - - pw.increaseIndent(); - mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); - pw.decreaseIndent(); - } - - - /** - * Internals. - */ - - @Override - protected String getWhatToString(int what) { - return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); - } - - @Override - protected String getLogRecString(Message msg) { - final String logLine = String.format( - "%s/%d %d %d %s [%s]", - mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index, - msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); - - final String richerLogLine = getWhatToString(msg.what) + " " + logLine; - mLog.log(richerLogLine); - if (DBG) { - Log.d(mTag, richerLogLine); - } - - mMsgStateLogger.reset(); - return logLine; - } - - @Override - protected boolean recordLogRec(Message msg) { - // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, - // and we already log any LinkProperties change that results in an - // invocation of IpClient.Callback#onLinkPropertiesChange(). - final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); - if (!shouldLog) { - mMsgStateLogger.reset(); - } - return shouldLog; - } - - private void logError(String fmt, Object... args) { - final String msg = "ERROR " + String.format(fmt, args); - Log.e(mTag, msg); - mLog.log(msg); - } - - // This needs to be called with care to ensure that our LinkProperties - // are in sync with the actual LinkProperties of the interface. For example, - // we should only call this if we know for sure that there are no IP addresses - // assigned to the interface, etc. - private void resetLinkProperties() { - mNetlinkTracker.clearLinkProperties(); - mConfiguration = null; - mDhcpResults = null; - mTcpBufferSizes = ""; - mHttpProxy = null; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - } - - private void recordMetric(final int type) { - // We may record error metrics prior to starting. - // Map this to IMMEDIATE_FAILURE_DURATION. - final long duration = (mStartTimeMillis > 0) - ? (SystemClock.elapsedRealtime() - mStartTimeMillis) - : IMMEDIATE_FAILURE_DURATION; - mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); - } - - // For now: use WifiStateMachine's historical notion of provisioned. - @VisibleForTesting - static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) { - // For historical reasons, we should connect even if all we have is - // an IPv4 address and nothing else. - if (lp.hasIPv4Address() || lp.isProvisioned()) { - return true; - } - if (config == null) { - return false; - } - - // When an InitialConfiguration is specified, ignore any difference with previous - // properties and instead check if properties observed match the desired properties. - return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); - } - - // TODO: Investigate folding all this into the existing static function - // LinkProperties.compareProvisioning() or some other single function that - // takes two LinkProperties objects and returns a ProvisioningChange - // object that is a correct and complete assessment of what changed, taking - // account of the asymmetries described in the comments in this function. - // Then switch to using it everywhere (IpReachabilityMonitor, etc.). - private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { - int delta; - InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; - final boolean wasProvisioned = isProvisioned(oldLp, config); - final boolean isProvisioned = isProvisioned(newLp, config); - - if (!wasProvisioned && isProvisioned) { - delta = PROV_CHANGE_GAINED_PROVISIONING; - } else if (wasProvisioned && isProvisioned) { - delta = PROV_CHANGE_STILL_PROVISIONED; - } else if (!wasProvisioned && !isProvisioned) { - delta = PROV_CHANGE_STILL_NOT_PROVISIONED; - } else { - // (wasProvisioned && !isProvisioned) - // - // Note that this is true even if we lose a configuration element - // (e.g., a default gateway) that would not be required to advance - // into provisioned state. This is intended: if we have a default - // router and we lose it, that's a sure sign of a problem, but if - // we connect to a network with no IPv4 DNS servers, we consider - // that to be a network without DNS servers and connect anyway. - // - // See the comment below. - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); - final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address(); - final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute(); - - // If bad wifi avoidance is disabled, then ignore IPv6 loss of - // provisioning. Otherwise, when a hotspot that loses Internet - // access sends out a 0-lifetime RA to its clients, the clients - // will disconnect and then reconnect, avoiding the bad hotspot, - // instead of getting stuck on the bad hotspot. http://b/31827713 . - // - // This is incorrect because if the hotspot then regains Internet - // access with a different prefix, TCP connections on the - // deprecated addresses will remain stuck. - // - // Note that we can still be disconnected by IpReachabilityMonitor - // if the IPv6 default gateway (but not the IPv6 DNS servers; see - // accompanying code in IpReachabilityMonitor) is unreachable. - final boolean ignoreIPv6ProvisioningLoss = - mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker - && mCm.getAvoidBadWifi(); - - // Additionally: - // - // Partial configurations (e.g., only an IPv4 address with no DNS - // servers and no default route) are accepted as long as DHCPv4 - // succeeds. On such a network, isProvisioned() will always return - // false, because the configuration is not complete, but we want to - // connect anyway. It might be a disconnected network such as a - // Chromecast or a wireless printer, for example. - // - // Because on such a network isProvisioned() will always return false, - // delta will never be LOST_PROVISIONING. So check for loss of - // provisioning here too. - if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - // Additionally: - // - // If the previous link properties had a global IPv6 address and an - // IPv6 default route then also consider the loss of that default route - // to be a loss of provisioning. See b/27962810. - if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - return delta; - } - - private void dispatchCallback(int delta, LinkProperties newLp) { - switch (delta) { - case PROV_CHANGE_GAINED_PROVISIONING: - if (DBG) { - Log.d(mTag, "onProvisioningSuccess()"); - } - recordMetric(IpManagerEvent.PROVISIONING_OK); - mCallback.onProvisioningSuccess(newLp); - break; - - case PROV_CHANGE_LOST_PROVISIONING: - if (DBG) { - Log.d(mTag, "onProvisioningFailure()"); - } - recordMetric(IpManagerEvent.PROVISIONING_FAIL); - mCallback.onProvisioningFailure(newLp); - break; - - default: - if (DBG) { - Log.d(mTag, "onLinkPropertiesChange()"); - } - mCallback.onLinkPropertiesChange(newLp); - break; - } - } - - // Updates all IpClient-related state concerned with LinkProperties. - // Returns a ProvisioningChange for possibly notifying other interested - // parties that are not fronted by IpClient. - private int setLinkProperties(LinkProperties newLp) { - if (mApfFilter != null) { - mApfFilter.setLinkProperties(newLp); - } - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.updateLinkProperties(newLp); - } - - int delta = compareProvisioning(mLinkProperties, newLp); - mLinkProperties = new LinkProperties(newLp); - - if (delta == PROV_CHANGE_GAINED_PROVISIONING) { - // TODO: Add a proper ProvisionedState and cancel the alarm in - // its enter() method. - mProvisioningTimeoutAlarm.cancel(); - } - - return delta; - } - - private LinkProperties assembleLinkProperties() { - // [1] Create a new LinkProperties object to populate. - LinkProperties newLp = new LinkProperties(); - newLp.setInterfaceName(mInterfaceName); - - // [2] Pull in data from netlink: - // - IPv4 addresses - // - IPv6 addresses - // - IPv6 routes - // - IPv6 DNS servers - // - // N.B.: this is fundamentally race-prone and should be fixed by - // changing NetlinkTracker from a hybrid edge/level model to an - // edge-only model, or by giving IpClient its own netlink socket(s) - // so as to track all required information directly. - LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); - newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); - for (RouteInfo route : netlinkLinkProperties.getRoutes()) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); - - // [3] Add in data from DHCPv4, if available. - // - // mDhcpResults is never shared with any other owner so we don't have - // to worry about concurrent modification. - if (mDhcpResults != null) { - for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); - newLp.setDomains(mDhcpResults.domains); - - if (mDhcpResults.mtu != 0) { - newLp.setMtu(mDhcpResults.mtu); - } - } - - // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. - if (!TextUtils.isEmpty(mTcpBufferSizes)) { - newLp.setTcpBufferSizes(mTcpBufferSizes); - } - if (mHttpProxy != null) { - newLp.setHttpProxy(mHttpProxy); - } - - // [5] Add data from InitialConfiguration - if (mConfiguration != null && mConfiguration.mInitialConfig != null) { - InitialConfiguration config = mConfiguration.mInitialConfig; - // Add InitialConfiguration routes and dns server addresses once all addresses - // specified in the InitialConfiguration have been observed with Netlink. - if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { - for (IpPrefix prefix : config.directlyConnectedRoutes) { - newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); - } - } - addAllReachableDnsServers(newLp, config.dnsServers); - } - final LinkProperties oldLp = mLinkProperties; - if (DBG) { - Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", - netlinkLinkProperties, newLp, oldLp)); - } - - // TODO: also learn via netlink routes specified by an InitialConfiguration and specified - // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. - return newLp; - } - - private static void addAllReachableDnsServers( - LinkProperties lp, Iterable<InetAddress> dnses) { - // TODO: Investigate deleting this reachability check. We should be - // able to pass everything down to netd and let netd do evaluation - // and RFC6724-style sorting. - for (InetAddress dns : dnses) { - if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { - lp.addDnsServer(dns); - } - } - } - - // Returns false if we have lost provisioning, true otherwise. - private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { - final LinkProperties newLp = assembleLinkProperties(); - if (Objects.equals(newLp, mLinkProperties)) { - return true; - } - final int delta = setLinkProperties(newLp); - if (sendCallbacks) { - dispatchCallback(delta, newLp); - } - return (delta != PROV_CHANGE_LOST_PROVISIONING); - } - - private void handleIPv4Success(DhcpResults dhcpResults) { - mDhcpResults = new DhcpResults(dhcpResults); - final LinkProperties newLp = assembleLinkProperties(); - final int delta = setLinkProperties(newLp); - - if (DBG) { - Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); - } - mCallback.onNewDhcpResults(dhcpResults); - dispatchCallback(delta, newLp); - } - - private void handleIPv4Failure() { - // TODO: Investigate deleting this clearIPv4Address() call. - // - // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances - // that could trigger a call to this function. If we missed handling - // that message in StartedState for some reason we would still clear - // any addresses upon entry to StoppedState. - mInterfaceCtrl.clearIPv4Address(); - mDhcpResults = null; - if (DBG) { - Log.d(mTag, "onNewDhcpResults(null)"); - } - mCallback.onNewDhcpResults(null); - - handleProvisioningFailure(); - } - - private void handleProvisioningFailure() { - final LinkProperties newLp = assembleLinkProperties(); - int delta = setLinkProperties(newLp); - // If we've gotten here and we're still not provisioned treat that as - // a total loss of provisioning. - // - // Either (a) static IP configuration failed or (b) DHCPv4 failed AND - // there was no usable IPv6 obtained before a non-zero provisioning - // timeout expired. - // - // Regardless: GAME OVER. - if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) { - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - dispatchCallback(delta, newLp); - if (delta == PROV_CHANGE_LOST_PROVISIONING) { - transitionTo(mStoppingState); - } - } - - private void doImmediateProvisioningFailure(int failureType) { - logError("onProvisioningFailure(): %s", failureType); - recordMetric(failureType); - mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); - } - - private boolean startIPv4() { - // If we have a StaticIpConfiguration attempt to apply it and - // handle the result accordingly. - if (mConfiguration.mStaticIpConfig != null) { - if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { - handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); - } else { - return false; - } - } else { - // Start DHCPv4. - mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams); - mDhcpClient.registerForPreDhcpNotification(); - mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); - } - - return true; - } - - private boolean startIPv6() { - return mInterfaceCtrl.setIPv6PrivacyExtensions(true) - && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) - && mInterfaceCtrl.enableIPv6(); - } - - private boolean applyInitialConfig(InitialConfiguration config) { - // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. - for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { - if (!mInterfaceCtrl.addAddress(addr)) return false; - } - - return true; - } - - private boolean startIpReachabilityMonitor() { - try { - // TODO: Fetch these parameters from settings, and install a - // settings observer to watch for update and re-program these - // parameters (Q: is this level of dynamic updatability really - // necessary or does reading from settings at startup suffice?). - final int numSolicits = 5; - final int interSolicitIntervalMs = 750; - setNeighborParameters(mDependencies.getNetd(), mInterfaceName, - numSolicits, interSolicitIntervalMs); - } catch (Exception e) { - mLog.e("Failed to adjust neighbor parameters", e); - // Carry on using the system defaults (currently: 3, 1000); - } - - try { - mIpReachabilityMonitor = new IpReachabilityMonitor( - mContext, - mInterfaceParams, - getHandler(), - mLog, - new IpReachabilityMonitor.Callback() { - @Override - public void notifyLost(InetAddress ip, String logMsg) { - mCallback.onReachabilityLost(logMsg); - } - }, - mConfiguration.mUsingMultinetworkPolicyTracker); - } catch (IllegalArgumentException iae) { - // Failed to start IpReachabilityMonitor. Log it and call - // onProvisioningFailure() immediately. - // - // See http://b/31038971. - logError("IpReachabilityMonitor failure: %s", iae); - mIpReachabilityMonitor = null; - } - - return (mIpReachabilityMonitor != null); - } - - private void stopAllIP() { - // We don't need to worry about routes, just addresses, because: - // - disableIpv6() will clear autoconf IPv6 routes as well, and - // - we don't get IPv4 routes from netlink - // so we neither react to nor need to wait for changes in either. - - mInterfaceCtrl.disableIPv6(); - mInterfaceCtrl.clearAllAddresses(); - } - - class StoppedState extends State { - @Override - public void enter() { - stopAllIP(); - - resetLinkProperties(); - if (mStartTimeMillis > 0) { - // Completed a life-cycle; send a final empty LinkProperties - // (cleared in resetLinkProperties() above) and record an event. - mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties)); - recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); - mStartTimeMillis = 0; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_TERMINATE_AFTER_STOP: - stopStateMachineUpdaters(); - quit(); - break; - - case CMD_STOP: - break; - - case CMD_START: - mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj; - transitionTo(mStartedState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: - mMulticastFiltering = (boolean) msg.obj; - break; - - case DhcpClient.CMD_ON_QUIT: - // Everything is already stopped. - logError("Unexpected CMD_ON_QUIT (already stopped)."); - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StoppingState extends State { - @Override - public void enter() { - if (mDhcpClient == null) { - // There's no DHCPv4 for which to wait; proceed to stopped. - deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED)); - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_JUMP_STOPPING_TO_STOPPED: - transitionTo(mStoppedState); - break; - - case CMD_STOP: - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_ON_QUIT: - mDhcpClient = null; - transitionTo(mStoppedState); - break; - - default: - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StartedState extends State { - @Override - public void enter() { - mStartTimeMillis = SystemClock.elapsedRealtime(); - - if (mConfiguration.mProvisioningTimeoutMs > 0) { - final long alarmTime = SystemClock.elapsedRealtime() - + mConfiguration.mProvisioningTimeoutMs; - mProvisioningTimeoutAlarm.schedule(alarmTime); - } - - if (readyToProceed()) { - deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING)); - } else { - // Clear all IPv4 and IPv6 before proceeding to RunningState. - // Clean up any leftover state from an abnormal exit from - // tethering or during an IpClient restart. - stopAllIP(); - } - } - - @Override - public void exit() { - mProvisioningTimeoutAlarm.cancel(); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_JUMP_STARTED_TO_RUNNING: - transitionTo(mRunningState); - break; - - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - if (readyToProceed()) { - transitionTo(mRunningState); - } - break; - - case EVENT_PROVISIONING_TIMEOUT: - handleProvisioningFailure(); - break; - - default: - // It's safe to process messages out of order because the - // only message that can both - // a) be received at this time and - // b) affect provisioning state - // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - - private boolean readyToProceed() { - return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address()); - } - } - - class RunningState extends State { - private ConnectivityPacketTracker mPacketTracker; - private boolean mDhcpActionInFlight; - - @Override - public void enter() { - ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); - apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; - apfConfig.multicastFilter = mMulticastFiltering; - // Get the Configuration for ApfFilter from Context - apfConfig.ieee802_3Filter = - mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); - apfConfig.ethTypeBlackList = - mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList); - mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); - // TODO: investigate the effects of any multicast filtering racing/interfering with the - // rest of this IP configuration startup. - if (mApfFilter == null) { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - - mPacketTracker = createPacketTracker(); - if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); - - if (mConfiguration.mEnableIPv6 && !startIPv6()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); - enqueueJumpToStoppingState(); - return; - } - - if (mConfiguration.mEnableIPv4 && !startIPv4()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); - enqueueJumpToStoppingState(); - return; - } - - final InitialConfiguration config = mConfiguration.mInitialConfig; - if ((config != null) && !applyInitialConfig(config)) { - // TODO introduce a new IpManagerEvent constant to distinguish this error case. - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - enqueueJumpToStoppingState(); - return; - } - - if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { - doImmediateProvisioningFailure( - IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); - enqueueJumpToStoppingState(); - return; - } - } - - @Override - public void exit() { - stopDhcpAction(); - - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.stop(); - mIpReachabilityMonitor = null; - } - - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); - mDhcpClient.doQuit(); - } - - if (mPacketTracker != null) { - mPacketTracker.stop(); - mPacketTracker = null; - } - - if (mApfFilter != null) { - mApfFilter.shutdown(); - mApfFilter = null; - } - - resetLinkProperties(); - } - - private void enqueueJumpToStoppingState() { - deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING)); - } - - private ConnectivityPacketTracker createPacketTracker() { - try { - return new ConnectivityPacketTracker( - getHandler(), mInterfaceParams, mConnectivityPacketLog); - } catch (IllegalArgumentException e) { - return null; - } - } - - private void ensureDhcpAction() { - if (!mDhcpActionInFlight) { - mCallback.onPreDhcpAction(); - mDhcpActionInFlight = true; - final long alarmTime = SystemClock.elapsedRealtime() - + mConfiguration.mRequestedPreDhcpActionMs; - mDhcpActionTimeoutAlarm.schedule(alarmTime); - } - } - - private void stopDhcpAction() { - mDhcpActionTimeoutAlarm.cancel(); - if (mDhcpActionInFlight) { - mCallback.onPostDhcpAction(); - mDhcpActionInFlight = false; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_JUMP_RUNNING_TO_STOPPING: - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case CMD_START: - logError("ALERT: START received in StartedState. Please fix caller."); - break; - - case CMD_CONFIRM: - // TODO: Possibly introduce a second type of confirmation - // that both probes (a) on-link neighbors and (b) does - // a DHCPv4 RENEW. We used to do this on Wi-Fi framework - // roams. - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.probeAll(); - } - break; - - case EVENT_PRE_DHCP_ACTION_COMPLETE: - // It's possible to reach here if, for example, someone - // calls completedPreDhcpAction() after provisioning with - // a static IP configuration. - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { - transitionTo(mStoppingState); - } - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: { - mMulticastFiltering = (boolean) msg.obj; - if (mApfFilter != null) { - mApfFilter.setMulticastFilter(mMulticastFiltering); - } else { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - break; - } - - case EVENT_READ_PACKET_FILTER_COMPLETE: { - if (mApfFilter != null) { - mApfFilter.setDataSnapshot((byte[]) msg.obj); - } - mApfDataSnapshotComplete.open(); - break; - } - - case EVENT_DHCPACTION_TIMEOUT: - stopDhcpAction(); - break; - - case DhcpClient.CMD_PRE_DHCP_ACTION: - if (mConfiguration.mRequestedPreDhcpActionMs > 0) { - ensureDhcpAction(); - } else { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { - final LinkAddress ipAddress = (LinkAddress) msg.obj; - if (mInterfaceCtrl.setIPv4Address(ipAddress)) { - mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); - } else { - logError("Failed to set IPv4 address."); - dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, - new LinkProperties(mLinkProperties)); - transitionTo(mStoppingState); - } - break; - } - - // This message is only received when: - // - // a) initial address acquisition succeeds, - // b) renew succeeds or is NAK'd, - // c) rebind succeeds or is NAK'd, or - // c) the lease expires, - // - // but never when initial address acquisition fails. The latter - // condition is now governed by the provisioning timeout. - case DhcpClient.CMD_POST_DHCP_ACTION: - stopDhcpAction(); - - switch (msg.arg1) { - case DhcpClient.DHCP_SUCCESS: - handleIPv4Success((DhcpResults) msg.obj); - break; - case DhcpClient.DHCP_FAILURE: - handleIPv4Failure(); - break; - default: - logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); - } - break; - - case DhcpClient.CMD_ON_QUIT: - // DHCPv4 quit early for some reason. - logError("Unexpected CMD_ON_QUIT."); - mDhcpClient = null; - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - private static class MessageHandlingLogger { - public String processedInState; - public String receivedInState; - - public void reset() { - processedInState = null; - receivedInState = null; - } - - public void handled(State processedIn, IState receivedIn) { - processedInState = processedIn.getClass().getSimpleName(); - receivedInState = receivedIn.getName(); - } - - public String toString() { - return String.format("rcvd_in=%s, proc_in=%s", - receivedInState, processedInState); - } - } - - private static void setNeighborParameters( - INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs) - throws RemoteException, IllegalArgumentException { - Preconditions.checkNotNull(netd); - Preconditions.checkArgument(!TextUtils.isEmpty(ifName)); - Preconditions.checkArgument(numSolicits > 0); - Preconditions.checkArgument(interSolicitIntervalMs > 0); - - for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) { - netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms", - Integer.toString(interSolicitIntervalMs)); - netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit", - Integer.toString(numSolicits)); - } - } - - // TODO: extract out into CollectionUtils. - static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { - for (T t : coll) { - if (fn.test(t)) { - return true; - } - } - return false; - } - - static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { - return !any(coll, not(fn)); - } - - static <T> Predicate<T> not(Predicate<T> fn) { - return (t) -> !fn.test(t); - } - - static <T> String join(String delimiter, Collection<T> coll) { - return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); - } - - static <T> T find(Iterable<T> coll, Predicate<T> fn) { - for (T t: coll) { - if (fn.test(t)) { - return t; - } - } - return null; - } - - static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) { - return coll.stream().filter(fn).collect(Collectors.toList()); - } } diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 0aec10149b23..2a2a67a92a86 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -16,8 +16,15 @@ package android.net.ip; +import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; + import android.content.Context; +import android.net.DhcpResultsParcelable; import android.net.LinkProperties; +import android.net.LinkPropertiesParcelable; +import android.net.NetworkStack; +import android.net.ip.IIpClientCallbacks; import android.os.ConditionVariable; import java.io.FileDescriptor; @@ -31,8 +38,8 @@ import java.io.PrintWriter; * @hide */ public class IpClientUtil { - // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg - public static final String DUMP_ARG = IpClient.DUMP_ARG; + // TODO: remove with its callers + public static final String DUMP_ARG = "ipclient"; /** * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is @@ -69,24 +76,129 @@ public class IpClientUtil { * * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of * {@link IIpClientCallbacks}. + * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} */ public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) { - // TODO: request IpClient asynchronously from NetworkStack. - final IpClient ipClient = new IpClient(context, ifName, callback); - callback.onIpClientCreated(ipClient.makeConnector()); + context.getSystemService(NetworkStack.class) + .makeIpClient(ifName, new IpClientCallbacksProxy(callback)); + } + + /** + * Create a new IpClient. + * + * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy} + * instead of {@link IIpClientCallbacks}. + * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} + */ + public static void makeIpClient( + Context context, String ifName, IpClientCallbacksProxy callback) { + context.getSystemService(NetworkStack.class) + .makeIpClient(ifName, callback); + } + + /** + * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}. + */ + public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub { + protected final IpClientCallbacks mCb; + + /** + * Create a new IpClientCallbacksProxy. + */ + public IpClientCallbacksProxy(IpClientCallbacks cb) { + mCb = cb; + } + + @Override + public void onIpClientCreated(IIpClient ipClient) { + mCb.onIpClientCreated(ipClient); + } + + @Override + public void onPreDhcpAction() { + mCb.onPreDhcpAction(); + } + + @Override + public void onPostDhcpAction() { + mCb.onPostDhcpAction(); + } + + // This is purely advisory and not an indication of provisioning + // success or failure. This is only here for callers that want to + // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress). + // DHCPv4 or static IPv4 configuration failure or success can be + // determined by whether or not the passed-in DhcpResults object is + // null or not. + @Override + public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) { + mCb.onNewDhcpResults(fromStableParcelable(dhcpResults)); + } + + @Override + public void onProvisioningSuccess(LinkPropertiesParcelable newLp) { + mCb.onProvisioningSuccess(fromStableParcelable(newLp)); + } + @Override + public void onProvisioningFailure(LinkPropertiesParcelable newLp) { + mCb.onProvisioningFailure(fromStableParcelable(newLp)); + } + + // Invoked on LinkProperties changes. + @Override + public void onLinkPropertiesChange(LinkPropertiesParcelable newLp) { + mCb.onLinkPropertiesChange(fromStableParcelable(newLp)); + } + + // Called when the internal IpReachabilityMonitor (if enabled) has + // detected the loss of a critical number of required neighbors. + @Override + public void onReachabilityLost(String logMsg) { + mCb.onReachabilityLost(logMsg); + } + + // Called when the IpClient state machine terminates. + @Override + public void onQuit() { + mCb.onQuit(); + } + + // Install an APF program to filter incoming packets. + @Override + public void installPacketFilter(byte[] filter) { + mCb.installPacketFilter(filter); + } + + // Asynchronously read back the APF program & data buffer from the wifi driver. + // Due to Wifi HAL limitations, the current implementation only supports dumping the entire + // buffer. In response to this request, the driver returns the data buffer asynchronously + // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message. + @Override + public void startReadPacketFilter() { + mCb.startReadPacketFilter(); + } + + // If multicast filtering cannot be accomplished with APF, this function will be called to + // actuate multicast filtering using another means. + @Override + public void setFallbackMulticastFilter(boolean enabled) { + mCb.setFallbackMulticastFilter(enabled); + } + + // Enabled/disable Neighbor Discover offload functionality. This is + // called, for example, whenever 464xlat is being started or stopped. + @Override + public void setNeighborDiscoveryOffload(boolean enable) { + mCb.setNeighborDiscoveryOffload(enable); + } } /** * Dump logs for the specified IpClient. - * TODO: remove logging from this method once IpClient logs are dumped in NetworkStack dumpsys, - * then remove callers and delete. + * TODO: remove callers and delete */ public static void dumpIpClient( IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) { - if (!(connector instanceof IpClient.IpClientConnector)) { - pw.println("Invalid connector"); - return; - } - ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args); + pw.println("IpClient logs have moved to dumpsys network_stack"); } } diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 7910c9a69310..f7360f52225f 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -40,7 +40,7 @@ import android.net.dhcp.IDhcpServer; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Looper; diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/shared/NetdService.java index 80b2c2705038..be0f5f2d4f34 100644 --- a/services/net/java/android/net/util/NetdService.java +++ b/services/net/java/android/net/shared/NetdService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.net.util; +package android.net.shared; import android.net.INetd; import android.os.RemoteException; diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java index 463cf2af2897..3d2a2de45539 100644 --- a/services/net/java/android/net/shared/NetworkMonitorUtils.java +++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java @@ -16,6 +16,11 @@ package android.net.shared; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; + import android.content.Context; import android.net.NetworkCapabilities; import android.provider.Settings; @@ -58,9 +63,12 @@ public class NetworkMonitorUtils { * @param dfltNetCap Default requested network capabilities. * @param nc Network capabilities of the network to test. */ - public static boolean isValidationRequired( - NetworkCapabilities dfltNetCap, NetworkCapabilities nc) { + public static boolean isValidationRequired(NetworkCapabilities nc) { // TODO: Consider requiring validation for DUN networks. - return dfltNetCap.satisfiedByNetworkCapabilities(nc); + return nc != null + && nc.hasCapability(NET_CAPABILITY_INTERNET) + && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) + && nc.hasCapability(NET_CAPABILITY_TRUSTED) + && nc.hasCapability(NET_CAPABILITY_NOT_VPN); } } diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java index 7b060da01a89..f6bb87369cad 100644 --- a/services/net/java/android/net/util/InterfaceParams.java +++ b/services/net/java/android/net/util/InterfaceParams.java @@ -16,9 +16,6 @@ package android.net.util; -import static android.net.util.NetworkConstants.ETHER_MTU; -import static android.net.util.NetworkConstants.IPV6_MIN_MTU; - import static com.android.internal.util.Preconditions.checkArgument; import android.net.MacAddress; @@ -44,6 +41,11 @@ public class InterfaceParams { public final MacAddress macAddr; public final int defaultMtu; + // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack. + private static final int ETHER_MTU = 1500; + private static final int IPV6_MIN_MTU = 1280; + + public static InterfaceParams getByName(String name) { final NetworkInterface netif = getNetworkInterfaceByName(name); if (netif == null) return null; diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index c183b81362dc..ea5ce65f6f79 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -28,28 +28,6 @@ package android.net.util; public final class NetworkConstants { private NetworkConstants() { throw new RuntimeException("no instance permitted"); } - /** - * Ethernet constants. - * - * See also: - * - https://tools.ietf.org/html/rfc894 - * - https://tools.ietf.org/html/rfc2464 - * - https://tools.ietf.org/html/rfc7042 - * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml - * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml - */ - public static final int ETHER_DST_ADDR_OFFSET = 0; - public static final int ETHER_SRC_ADDR_OFFSET = 6; - public static final int ETHER_ADDR_LEN = 6; - - public static final int ETHER_TYPE_OFFSET = 12; - public static final int ETHER_TYPE_LENGTH = 2; - public static final int ETHER_TYPE_ARP = 0x0806; - public static final int ETHER_TYPE_IPV4 = 0x0800; - public static final int ETHER_TYPE_IPV6 = 0x86dd; - - public static final int ETHER_HEADER_LEN = 14; - public static final byte FF = asByte(0xff); public static final byte[] ETHER_ADDR_BROADCAST = { FF, FF, FF, FF, FF, FF @@ -58,34 +36,12 @@ public final class NetworkConstants { public static final int ETHER_MTU = 1500; /** - * ARP constants. - * - * See also: - * - https://tools.ietf.org/html/rfc826 - * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml - */ - public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. - public static final int ARP_REQUEST = 1; - public static final int ARP_REPLY = 2; - public static final int ARP_HWTYPE_RESERVED_LO = 0; - public static final int ARP_HWTYPE_ETHER = 1; - public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; - - /** * IPv4 constants. * * See also: * - https://tools.ietf.org/html/rfc791 */ - public static final int IPV4_HEADER_MIN_LEN = 20; - public static final int IPV4_IHL_MASK = 0xf; - public static final int IPV4_FLAGS_OFFSET = 6; - public static final int IPV4_FRAGMENT_MASK = 0x1fff; - public static final int IPV4_PROTOCOL_OFFSET = 9; - public static final int IPV4_SRC_ADDR_OFFSET = 12; - public static final int IPV4_DST_ADDR_OFFSET = 16; public static final int IPV4_ADDR_BITS = 32; - public static final int IPV4_ADDR_LEN = 4; /** * IPv6 constants. @@ -93,15 +49,10 @@ public final class NetworkConstants { * See also: * - https://tools.ietf.org/html/rfc2460 */ - public static final int IPV6_HEADER_LEN = 40; - public static final int IPV6_PROTOCOL_OFFSET = 6; - public static final int IPV6_SRC_ADDR_OFFSET = 8; - public static final int IPV6_DST_ADDR_OFFSET = 24; public static final int IPV6_ADDR_BITS = 128; public static final int IPV6_ADDR_LEN = 16; public static final int IPV6_MIN_MTU = 1280; public static final int RFC7421_PREFIX_LENGTH = 64; - public static final int RFC6177_MIN_PREFIX_LENGTH = 48; /** * ICMP common (v4/v6) constants. @@ -124,45 +75,7 @@ public final class NetworkConstants { * - https://tools.ietf.org/html/rfc792 */ public static final int ICMPV4_ECHO_REQUEST_TYPE = 8; - - /** - * ICMPv6 constants. - * - * See also: - * - https://tools.ietf.org/html/rfc4443 - * - https://tools.ietf.org/html/rfc4861 - */ - public static final int ICMPV6_HEADER_MIN_LEN = 4; public static final int ICMPV6_ECHO_REQUEST_TYPE = 128; - public static final int ICMPV6_ECHO_REPLY_TYPE = 129; - public static final int ICMPV6_ROUTER_SOLICITATION = 133; - public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; - public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; - public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; - - public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; - public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; - public static final int ICMPV6_ND_OPTION_SLLA = 1; - public static final int ICMPV6_ND_OPTION_TLLA = 2; - public static final int ICMPV6_ND_OPTION_MTU = 5; - - - /** - * UDP constants. - * - * See also: - * - https://tools.ietf.org/html/rfc768 - */ - public static final int UDP_HEADER_LEN = 8; - - /** - * DHCP(v4) constants. - * - * See also: - * - https://tools.ietf.org/html/rfc2131 - */ - public static final int DHCP4_SERVER_PORT = 67; - public static final int DHCP4_CLIENT_PORT = 68; /** * DNS constants. @@ -176,9 +89,4 @@ public final class NetworkConstants { * Utility functions. */ public static byte asByte(int i) { return (byte) i; } - - public static String asString(int i) { return Integer.toString(i); } - - public static int asUint(byte b) { return (b & 0xff); } - public static int asUint(short s) { return (s & 0xffff); } } diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index c034c3831524..6fae9a51830f 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -13,6 +13,7 @@ android_test { ], static_libs: [ + "frameworks-base-testutils", "androidx.test.runner", "mockito-target-minus-junit4", "platform-test-annotations", diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 51532bc8ca5c..fb8f3e78ff84 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2311,6 +2311,9 @@ public final class Telephony { /** * Contains message parts. + * + * To avoid issues where applications might cache a part ID, the ID of a deleted part must + * not be reused to point at a new part. */ public static final class Part implements BaseColumns { @@ -2322,6 +2325,12 @@ public final class Telephony { } /** + * The {@code content://} style URL for this table. Can be appended with a part ID to + * address individual parts. + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part"); + + /** * The identifier of the message which this part belongs to. * <P>Type: INTEGER</P> */ diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index 7b29f69da6da..30e641d61143 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -148,8 +148,9 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P /** * Return the Bit Error Rate - * @returns the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or UNAVAILABLE. - * @hide + * + * @return the bit error rate (0-7, 99) as defined in TS 27.007 8.5 or + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}. */ public int getBitErrorRate() { return mBitErrorRate; diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index 85c53f243037..ca264f738e1d 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -961,6 +961,13 @@ public final class DataFailCause { /** @hide */ public static final int RESET_BY_FRAMEWORK = 0x10005; + /** + * Data handover failed. + * + * @hide + */ + public static final int HANDOVER_FAILED = 0x10006; + /** @hide */ @IntDef(value = { NONE, diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 421851be3e57..10647e672b6c 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1410,47 +1410,49 @@ public class ServiceState implements Parcelable { } /** @hide */ - public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rt) { - switch(rt) { - case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: - return TelephonyManager.NETWORK_TYPE_GPRS; - case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: - return TelephonyManager.NETWORK_TYPE_EDGE; - case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: - return TelephonyManager.NETWORK_TYPE_UMTS; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: - return TelephonyManager.NETWORK_TYPE_HSDPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: - return TelephonyManager.NETWORK_TYPE_HSUPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: - return TelephonyManager.NETWORK_TYPE_HSPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: - case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: - return TelephonyManager.NETWORK_TYPE_CDMA; - case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: - return TelephonyManager.NETWORK_TYPE_1xRTT; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: - return TelephonyManager.NETWORK_TYPE_EVDO_0; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: - return TelephonyManager.NETWORK_TYPE_EVDO_A; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: - return TelephonyManager.NETWORK_TYPE_EVDO_B; - case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: - return TelephonyManager.NETWORK_TYPE_EHRPD; - case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: - return TelephonyManager.NETWORK_TYPE_LTE; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: - return TelephonyManager.NETWORK_TYPE_HSPAP; - case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: - return TelephonyManager.NETWORK_TYPE_GSM; - case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA: - return TelephonyManager.NETWORK_TYPE_TD_SCDMA; - case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN: - return TelephonyManager.NETWORK_TYPE_IWLAN; - case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: - return TelephonyManager.NETWORK_TYPE_LTE_CA; - default: - return TelephonyManager.NETWORK_TYPE_UNKNOWN; + public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rat) { + switch(rat) { + case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: + return TelephonyManager.NETWORK_TYPE_GPRS; + case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: + return TelephonyManager.NETWORK_TYPE_EDGE; + case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: + return TelephonyManager.NETWORK_TYPE_UMTS; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: + return TelephonyManager.NETWORK_TYPE_HSDPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: + return TelephonyManager.NETWORK_TYPE_HSUPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: + return TelephonyManager.NETWORK_TYPE_HSPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: + return TelephonyManager.NETWORK_TYPE_CDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: + return TelephonyManager.NETWORK_TYPE_1xRTT; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: + return TelephonyManager.NETWORK_TYPE_EVDO_0; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: + return TelephonyManager.NETWORK_TYPE_EVDO_A; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: + return TelephonyManager.NETWORK_TYPE_EVDO_B; + case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: + return TelephonyManager.NETWORK_TYPE_EHRPD; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: + return TelephonyManager.NETWORK_TYPE_LTE; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: + return TelephonyManager.NETWORK_TYPE_HSPAP; + case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: + return TelephonyManager.NETWORK_TYPE_GSM; + case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA: + return TelephonyManager.NETWORK_TYPE_TD_SCDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN: + return TelephonyManager.NETWORK_TYPE_IWLAN; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: + return TelephonyManager.NETWORK_TYPE_LTE_CA; + case ServiceState.RIL_RADIO_TECHNOLOGY_NR: + return TelephonyManager.NETWORK_TYPE_NR; + default: + return TelephonyManager.NETWORK_TYPE_UNKNOWN; } } diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index ad3ca6d129c9..91375bc2f11e 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -292,14 +292,27 @@ public class SignalStrength implements Parcelable { * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 * * @return RSSI in ASU 0..31, 99, or UNAVAILABLE + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getAsuLevel}. + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths */ + @Deprecated public int getGsmSignalStrength() { return mGsm.getAsuLevel(); } /** * Get the GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5 + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getBitErrorRate}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getGsmBitErrorRate() { return mGsm.getBitErrorRate(); } @@ -308,14 +321,28 @@ public class SignalStrength implements Parcelable { * Get the CDMA RSSI value in dBm * * @return the CDMA RSSI value or {@link #INVALID} if invalid + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getCdmaDbm}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getCdmaDbm() { return mCdma.getCdmaDbm(); } /** * Get the CDMA Ec/Io value in dB*10 + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getCdmaEcio}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getCdmaEcio() { return mCdma.getCdmaEcio(); } @@ -324,51 +351,112 @@ public class SignalStrength implements Parcelable { * Get the EVDO RSSI value in dBm * * @return the EVDO RSSI value or {@link #INVALID} if invalid + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoDbm}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getEvdoDbm() { return mCdma.getEvdoDbm(); } /** * Get the EVDO Ec/Io value in dB*10 + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoEcio}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getEvdoEcio() { return mCdma.getEvdoEcio(); } /** * Get the signal to noise ratio. Valid values are 0-8. 8 is the highest. + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoSnr}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() */ + @Deprecated public int getEvdoSnr() { return mCdma.getEvdoSnr(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRssi}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteSignalStrength() { return mLte.getRssi(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRsrp}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteRsrp() { return mLte.getRsrp(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRsrq}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteRsrq() { return mLte.getRsrq(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getRssnr}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteRssnr() { return mLte.getRssnr(); } - /** @hide */ - @UnsupportedAppUsage + /** + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getCqi}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() + * @hide + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteCqi() { return mLte.getCqi(); } @@ -391,11 +479,17 @@ public class SignalStrength implements Parcelable { } /** - * Get the signal level as an asu value between 0..31, 99 is unknown + * Get the signal level as an asu value with a range dependent on the underlying technology. * + * @deprecated this information should be retrieved from + * {@link CellSignalStrength#getAsuLevel}. Because the levels vary by technology, + * this method is misleading and should not be used. + * @see android.telephony#CellSignalStrength + * @see android.telephony.SignalStrength#getCellSignalStrengths * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getAsuLevel() { return getPrimary().getAsuLevel(); } @@ -403,9 +497,15 @@ public class SignalStrength implements Parcelable { /** * Get the signal strength as dBm * + * @deprecated this information should be retrieved from + * {@link CellSignalStrength#getDbm()}. Because the levels vary by technology, + * this method is misleading and should not be used. + * @see android.telephony#CellSignalStrength + * @see android.telephony.SignalStrength#getCellSignalStrengths * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getDbm() { return getPrimary().getDbm(); } @@ -413,9 +513,15 @@ public class SignalStrength implements Parcelable { /** * Get Gsm signal strength as dBm * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getDbm}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getGsmDbm() { return mGsm.getDbm(); } @@ -423,9 +529,15 @@ public class SignalStrength implements Parcelable { /** * Get gsm as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getLevel}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getGsmLevel() { return mGsm.getLevel(); } @@ -433,9 +545,15 @@ public class SignalStrength implements Parcelable { /** * Get the gsm signal level as an asu value between 0..31, 99 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthGsm#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthGsm + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getGsmAsuLevel() { return mGsm.getAsuLevel(); } @@ -443,9 +561,15 @@ public class SignalStrength implements Parcelable { /** * Get cdma as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getLevel}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getCdmaLevel() { return mCdma.getLevel(); } @@ -453,9 +577,17 @@ public class SignalStrength implements Parcelable { /** * Get the cdma signal level as an asu value between 0..31, 99 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getAsuLevel}. Since there is no definition of + * ASU for CDMA, the resultant value is Android-specific and is not recommended + * for use. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getCdmaAsuLevel() { return mCdma.getAsuLevel(); } @@ -463,9 +595,15 @@ public class SignalStrength implements Parcelable { /** * Get Evdo as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoLevel}. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getEvdoLevel() { return mCdma.getEvdoLevel(); } @@ -473,9 +611,17 @@ public class SignalStrength implements Parcelable { /** * Get the evdo signal level as an asu value between 0..31, 99 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthCdma#getEvdoAsuLevel}. Since there is no definition of + * ASU for EvDO, the resultant value is Android-specific and is not recommended + * for use. + * + * @see android.telephony#CellSignalStrengthCdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getEvdoAsuLevel() { return mCdma.getEvdoAsuLevel(); } @@ -483,9 +629,15 @@ public class SignalStrength implements Parcelable { /** * Get LTE as dBm * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getDbm}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteDbm() { return mLte.getRsrp(); } @@ -493,9 +645,15 @@ public class SignalStrength implements Parcelable { /** * Get LTE as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getLevel}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteLevel() { return mLte.getLevel(); } @@ -504,26 +662,46 @@ public class SignalStrength implements Parcelable { * Get the LTE signal level as an asu value between 0..97, 99 is unknown * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthLte#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthLte + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getLteAsuLevel() { return mLte.getAsuLevel(); } /** * @return true if this is for GSM + * + * @deprecated This method returns true if there are any 3gpp type SignalStrength elements in + * this SignalStrength report or if the report contains no valid SignalStrength + * information. Instead callers should use + * {@link android.telephony.SignalStrength#getCellSignalStrengths + * getCellSignalStrengths()} to determine which types of information are contained + * in the SignalStrength report. */ + @Deprecated public boolean isGsm() { return !(getPrimary() instanceof CellSignalStrengthCdma); } /** - * @return get TD_SCDMA dbm + * @return get TD-SCDMA dBm + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthTdscdma#getDbm}. * + * @see android.telephony#CellSignalStrengthTdscdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getTdScdmaDbm() { return mTdscdma.getRscp(); } @@ -534,9 +712,15 @@ public class SignalStrength implements Parcelable { * INT_MAX: 0x7FFFFFFF denotes invalid value * Reference: 3GPP TS 25.123, section 9.1.1.1 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthTdscdma#getLevel}. + * + * @see android.telephony#CellSignalStrengthTdscdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getTdScdmaLevel() { return mTdscdma.getLevel(); } @@ -544,18 +728,30 @@ public class SignalStrength implements Parcelable { /** * Get the TD-SCDMA signal level as an asu value. * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthTdscdma#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthTdscdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public int getTdScdmaAsuLevel() { return mTdscdma.getAsuLevel(); } /** - * Gets WCDMA RSCP as a dbm value between -120 and -24, as defined in TS 27.007 8.69. + * Gets WCDMA RSCP as a dBm value between -120 and -24, as defined in TS 27.007 8.69. + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getRscp}. * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaRscp() { return mWcdma.getRscp(); } @@ -563,8 +759,14 @@ public class SignalStrength implements Parcelable { /** * Get the WCDMA signal level as an ASU value between 0-96, 255 is unknown * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getAsuLevel}. + * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaAsuLevel() { /* * 3GPP 27.007 (Ver 10.3.0) Sec 8.69 @@ -578,10 +780,16 @@ public class SignalStrength implements Parcelable { } /** - * Gets WCDMA signal strength as a dbm value between -120 and -24, as defined in TS 27.007 8.69. + * Gets WCDMA signal strength as a dBm value between -120 and -24, as defined in TS 27.007 8.69. + * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getDbm}. * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaDbm() { return mWcdma.getDbm(); } @@ -589,13 +797,19 @@ public class SignalStrength implements Parcelable { /** * Get WCDMA as level 0..4 * + * @deprecated this information should be retrieved from + * {@link CellSignalStrengthWcdma#getDbm}. + * + * @see android.telephony#CellSignalStrengthWcdma + * @see android.telephony.SignalStrength#getCellSignalStrengths() * @hide */ + @Deprecated public int getWcdmaLevel() { return mWcdma.getLevel(); } - /** + /** * @return hash code */ @Override @@ -639,9 +853,13 @@ public class SignalStrength implements Parcelable { * Set SignalStrength based on intent notifier map * * @param m intent notifier map + * + * @deprecated this method relies on non-stable implementation details, and full access to + * internal storage is available via {@link getCellSignalStrengths()}. * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private void setFromNotifierBundle(Bundle m) { mCdma = m.getParcelable("Cdma"); mGsm = m.getParcelable("Gsm"); @@ -654,9 +872,13 @@ public class SignalStrength implements Parcelable { * Set intent notifier Bundle based on SignalStrength * * @param m intent notifier Bundle + * + * @deprecated this method relies on non-stable implementation details, and full access to + * internal storage is available via {@link getCellSignalStrengths()}. * @hide */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public void fillInNotifierBundle(Bundle m) { m.putParcelable("Cdma", mCdma); m.putParcelable("Gsm", mGsm); diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 1378bb004696..d777bf123b67 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -22,6 +22,10 @@ import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothMapClient; +import android.bluetooth.BluetoothProfile; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; @@ -32,6 +36,7 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.telecom.PhoneAccount; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -61,6 +66,8 @@ import java.util.Map; */ public final class SmsManager { private static final String TAG = "SmsManager"; + private static final boolean DBG = false; + /** * A psuedo-subId that represents the default subId at any given time. The actual subId it * represents changes as the default subId is changed. @@ -339,6 +346,44 @@ public final class SmsManager { throw new IllegalArgumentException("Invalid message body"); } + // A Manager code accessing another manager is *not* acceptable, in Android. + // In this particular case, it is unavoidable because of the following: + // If the subscription for this SmsManager instance belongs to a remote SIM + // then a listener to get BluetoothMapClient proxy needs to be started up. + // Doing that is possible only in a foreground thread or as a system user. + // i.e., Can't be done in ISms service. + // For that reason, SubscriptionManager needs to be accessed here to determine + // if the subscription belongs to a remote SIM. + // Ideally, there should be another API in ISms to service messages going thru + // remote SIM subscriptions (and ISms should be tweaked to be able to access + // BluetoothMapClient proxy) + Context context = ActivityThread.currentApplication().getApplicationContext(); + SubscriptionManager manager = (SubscriptionManager) context + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int subId = getSubscriptionId(); + SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId); + if (DBG) { + Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info); + } + if (info == null) { + // There is no subscription for the given subId. That can only mean one thing: + // the caller is using a SmsManager instance with an obsolete subscription id. + // That is most probably because caller didn't invalidate SmsManager instance + // for an already deleted subscription id. + Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete."); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + } + + /* If the Subscription associated with this SmsManager instance belongs to a remote-sim, + * then send the message thru the remote-sim subscription. + */ + if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) { + if (DBG) Log.d(TAG, "sending message thru bluetooth"); + sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent, + deliveryIntent, info); + return; + } + try { ISms iccISms = getISmsServiceOrThrow(); iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(), @@ -350,6 +395,79 @@ public final class SmsManager { } } + private void sendTextMessageBluetooth(String destAddr, String scAddress, + String text, PendingIntent sentIntent, PendingIntent deliveryIntent, + SubscriptionInfo info) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter == null) { + // No bluetooth service on this platform? + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId()); + if (device == null) { + if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId()); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(), + new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent), + BluetoothProfile.MAP_CLIENT); + } + + private class MapMessageSender implements BluetoothProfile.ServiceListener { + final Uri[] mDestAddr; + private String mMessage; + final BluetoothDevice mDevice; + final PendingIntent mSentIntent; + final PendingIntent mDeliveryIntent; + MapMessageSender(final String destAddr, final String message, final BluetoothDevice device, + final PendingIntent sentIntent, final PendingIntent deliveryIntent) { + super(); + mDestAddr = new Uri[] {new Uri.Builder() + .appendPath(destAddr) + .scheme(PhoneAccount.SCHEME_TEL) + .build()}; + mMessage = message; + mDevice = device; + mSentIntent = sentIntent; + mDeliveryIntent = deliveryIntent; + } + + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (DBG) Log.d(TAG, "Service connected"); + if (profile != BluetoothProfile.MAP_CLIENT) return; + BluetoothMapClient mapProfile = (BluetoothMapClient) proxy; + if (mMessage != null) { + if (DBG) Log.d(TAG, "Sending message thru bluetooth"); + mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent); + mMessage = null; + } + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile); + } + + @Override + public void onServiceDisconnected(int profile) { + if (mMessage != null) { + if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message"); + sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + mMessage = null; + } + } + } + + private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) { + try { + intent.send(errorCode); + } catch (PendingIntent.CanceledException e) { + // PendingIntent is cancelled. ignore sending this error code back to + // caller. + if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage()); + } + } + /** * Send a text based SMS without writing it into the SMS Provider. * @@ -888,8 +1006,6 @@ public final class SmsManager { } } - - /** * Get the SmsManager associated with the default subscription id. The instance will always be * associated with the default subscription id, even if the default subscription id is changed. diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 4a25818c82b8..4e4ef4d6c236 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -184,6 +184,11 @@ public class SubscriptionInfo implements Parcelable { private int mProfileClass; /** + * Type of subscription + */ + private int mSubscriptionType; + + /** * @hide */ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, @@ -206,7 +211,8 @@ public class SubscriptionInfo implements Parcelable { @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, - isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass); + isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass, + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); } /** @@ -217,7 +223,7 @@ public class SubscriptionInfo implements Parcelable { Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, - boolean isGroupDisabled, int carrierid, int profileClass) { + boolean isGroupDisabled, int carrierId, int profileClass, int subType) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -239,11 +245,11 @@ public class SubscriptionInfo implements Parcelable { this.mGroupUUID = groupUUID; this.mIsMetered = isMetered; this.mIsGroupDisabled = isGroupDisabled; - this.mCarrierId = carrierid; + this.mCarrierId = carrierId; this.mProfileClass = profileClass; + this.mSubscriptionType = subType; } - /** * @return the subscription ID. */ @@ -487,6 +493,16 @@ public class SubscriptionInfo implements Parcelable { } /** + * This method returns the type of a subscription. It can be + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @return the type of subscription + */ + public @SubscriptionManager.SubscriptionType int getSubscriptionType() { + return mSubscriptionType; + } + + /** * Checks whether the app with the given context is authorized to manage this subscription * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} * returns true). @@ -611,11 +627,12 @@ public class SubscriptionInfo implements Parcelable { boolean isGroupDisabled = source.readBoolean(); int carrierid = source.readInt(); int profileClass = source.readInt(); + int subType = source.readInt(); return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID, - isMetered, isGroupDisabled, carrierid, profileClass); + isMetered, isGroupDisabled, carrierid, profileClass, subType); } @Override @@ -649,6 +666,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); + dest.writeInt(mSubscriptionType); } @Override @@ -685,7 +703,8 @@ public class SubscriptionInfo implements Parcelable { + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled - + " profileClass=" + mProfileClass + "}"; + + " profileClass=" + mProfileClass + + " subscriptionType=" + mSubscriptionType + "}"; } @Override diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 6724c034fc23..845d23ee4cc2 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -247,7 +247,9 @@ public class SubscriptionManager { public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; /** - * TelephonyProvider column name for SIM ICC Identifier + * TelephonyProvider column name for a unique identifier for the subscription within the + * specific subscription type. For example, it contains SIM ICC Identifier subscriptions + * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices. * <P>Type: TEXT (String)</P> */ /** @hide */ @@ -265,6 +267,63 @@ public class SubscriptionManager { public static final int SIM_NOT_INSERTED = -1; /** + * The slot-index for Bluetooth Remote-SIM subscriptions + * @hide + */ + public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX; + + /** + * TelephonyProvider column name Subscription-type. + * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions, + * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions. + * Default value is 0. + */ + /** @hide */ + public static final String SUBSCRIPTION_TYPE = "subscription_type"; + + /** + * This constant is to designate a subscription as a Local-SIM Subscription. + * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the + * device. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; + + /** + * This constant is to designate a subscription as a Remote-SIM Subscription. + * <p> + * A Remote-SIM subscription is for a SIM on a phone connected to this device via some + * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can + * be used for SMS, Voice and data by proxying data through the connected device. + * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs. + * </p> + * + * <p> + * A Remote-SIM is available only as long the phone stays connected to this device. + * When the phone disconnects, Remote-SIM subscription is removed from this device and is + * no longer known. All data associated with the subscription, such as stored SMS, call logs, + * contacts etc, are removed from this device. + * </p> + * + * <p> + * If the phone re-connects to this device, a new Remote-SIM subscription is created for + * the phone. The Subscription Id associated with the new subscription is different from + * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the + * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that + * was never seen before. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SUBSCRIPTION_TYPE_"}, + value = { + SUBSCRIPTION_TYPE_LOCAL_SIM, + SUBSCRIPTION_TYPE_REMOTE_SIM}) + public @interface SubscriptionType {} + + /** * TelephonyProvider column name for user displayed name. * <P>Type: TEXT (String)</P> */ @@ -1146,7 +1205,7 @@ public class SubscriptionManager { } /** - * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted + * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. * * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} @@ -1428,21 +1487,84 @@ public class SubscriptionManager { logd("[addSubscriptionInfoRecord]- invalid slotIndex"); } + addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM); + + // FIXME: Always returns null? + return null; + + } + + /** + * Add a new SubscriptionInfo to SubscriptionInfo database if needed + * @param uniqueId This is the unique identifier for the subscription within the + * specific subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType + * of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex, + int subscriptionType) { + if (VDBG) { + logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", displayName:" + displayName + ", slotIndex:" + slotIndex + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + try { ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); - if (iSub != null) { - // FIXME: This returns 1 on success, 0 on error should should we return it? - iSub.addSubInfoRecord(iccId, slotIndex); + if (iSub == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result); } else { - logd("[addSubscriptionInfoRecord]- ISub service is null"); + logd("successfully added new subscription"); } } catch (RemoteException ex) { // ignore it } + } - // FIXME: Always returns null? - return null; + /** + * Remove SubscriptionInfo record from the SubscriptionInfo database + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) { + if (VDBG) { + logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub == null) { + Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.removeSubInfo(uniqueId, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result); + } else { + logd("successfully removed subscription"); + } + } catch (RemoteException ex) { + // ignore it + } } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 51242051eb57..b9ffd4d21890 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -835,6 +835,7 @@ public class TelephonyManager { * @see TelephonyManager#NETWORK_TYPE_LTE * @see TelephonyManager#NETWORK_TYPE_EHRPD * @see TelephonyManager#NETWORK_TYPE_HSPAP + * @see TelephonyManager#NETWORK_TYPE_NR * * <p class="note"> * Retrieve with @@ -2306,6 +2307,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_LTE * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP + * @see #NETWORK_TYPE_NR * * @hide */ @@ -2357,6 +2359,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_LTE * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP + * @see #NETWORK_TYPE_NR */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @@ -2543,6 +2546,8 @@ public class TelephonyManager { return "IWLAN"; case NETWORK_TYPE_LTE_CA: return "LTE_CA"; + case NETWORK_TYPE_NR: + return "NR"; default: return "UNKNOWN"; } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 577ddbda50fa..04ec3d1a3df3 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -52,8 +52,8 @@ interface ISub { /** * Get the active SubscriptionInfo associated with the slotIndex * @param slotIndex the slot which the subscription is inserted - * @param callingPackage The package maing the call. - * @return SubscriptionInfo, maybe null if its not active + * @param callingPackage The package making the call. + * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex. */ SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage); @@ -115,6 +115,26 @@ interface ISub { int addSubInfoRecord(String iccId, int slotIndex); /** + * Add a new subscription info record, if needed + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this device + * @param subscriptionType the type of subscription to be added. + * @return 0 if success, < 0 on error. + */ + int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType); + + /** + * Remove subscription info record for the given device. + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the type of subscription to be removed + * @return 0 if success, < 0 on error. + */ + int removeSubInfo(String uniqueId, int subscriptionType); + + /** * Set SIM icon tint color by simInfo index * @param tint the icon tint color of the SIM * @param subId the unique SubscriptionInfo index in database diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 685067377166..7e1b4008c473 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -32,74 +32,6 @@ LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform -# These are not normally accessible from apps so they must be explicitly included. -LOCAL_JNI_SHARED_LIBRARIES := \ - android.hidl.token@1.0 \ - libartbase \ - libbacktrace \ - libbase \ - libbinder \ - libbinderthreadstate \ - libc++ \ - libcrypto \ - libcutils \ - libdexfile \ - libframeworksnettestsjni \ - libhidl-gen-utils \ - libhidlbase \ - libhidltransport \ - libhwbinder \ - liblog \ - liblzma \ - libnativehelper \ - libpackagelistparser \ - libpcre2 \ - libprocessgroup \ - libselinux \ - libui \ - libutils \ - libvintf \ - libvndksupport \ - libtinyxml2 \ - libunwindstack \ - libutilscallstack \ - libziparchive \ - libz \ - netd_aidl_interface-cpp - LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk include $(BUILD_PACKAGE) - -######################################################################### -# Build JNI Shared Library -######################################################################### - -LOCAL_PATH:= $(LOCAL_PATH)/jni - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_CFLAGS := -Wall -Wextra -Werror - -LOCAL_C_INCLUDES := \ - libpcap \ - hardware/google/apf - -LOCAL_SRC_FILES := $(call all-cpp-files-under) - -LOCAL_SHARED_LIBRARIES := \ - libbinder \ - liblog \ - libcutils \ - libnativehelper \ - netd_aidl_interface-cpp - -LOCAL_STATIC_LIBRARIES := \ - libpcap \ - libapf - -LOCAL_MODULE := libframeworksnettestsjni - -include $(BUILD_SHARED_LIBRARY) diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java new file mode 100644 index 000000000000..032e52666970 --- /dev/null +++ b/tests/net/java/android/net/DnsPacketTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsPacketTest { + private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag, + int qCount, int aCount, int nsCount, int arCount) { + assertEquals(header.id, id); + assertEquals(header.flags, flag); + assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount); + assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount); + assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount); + assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount); + } + + private void assertSectionParses(DnsPacket.DnsSection section, String dname, + int dtype, int dclass, int ttl, byte[] rr) { + assertEquals(section.dName, dname); + assertEquals(section.nsType, dtype); + assertEquals(section.nsClass, dclass); + assertEquals(section.ttl, ttl); + assertTrue(Arrays.equals(section.getRR(), rr)); + } + + class TestDnsPacket extends DnsPacket { + TestDnsPacket(byte[] data) throws ParseException { + super(data); + } + + public DnsHeader getHeader() { + return mHeader; + } + public List<DnsSection> getSectionList(int secType) { + return mSections[secType]; + } + } + + @Test + public void testNullDisallowed() { + try { + new TestDnsPacket(null); + fail("Exception not thrown for null byte array"); + } catch (DnsPacket.ParseException e) { + } + } + + @Test + public void testV4Answer() throws Exception { + final byte[] v4blob = new byte[] { + /* Header */ + 0x55, 0x66, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v4blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b, + new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); + } + + @Test + public void testV6Answer() throws Exception { + final byte[] v6blob = new byte[] { + /* Header */ + 0x77, 0x22, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x37, /* TTL */ + 0x00, 0x10, /* Data length */ + 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v6blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37, + new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 1c264184db4c..b5d5f61b527e 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -107,6 +107,8 @@ import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; import android.net.IpPrefix; +import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MatchAllNetworkSpecifier; @@ -122,6 +124,7 @@ import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.SocketKeepalive; import android.net.UidRange; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; @@ -186,6 +189,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -402,8 +407,8 @@ public class ConnectivityServiceTest { private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; - private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; - private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; + private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED; + private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; private Integer mExpectedKeepaliveSlot = null; // Contains the redirectUrl from networkStatus(). Before reading, wait for // mNetworkStatusReceived. @@ -1508,6 +1513,12 @@ public class ConnectivityServiceTest { verifyActiveNetwork(TRANSPORT_WIFI); } + @Test + public void testRequiresValidation() { + assertTrue(NetworkMonitorUtils.isValidationRequired( + mCm.getDefaultRequest().networkCapabilities)); + } + enum CallbackState { NONE, AVAILABLE, @@ -3542,6 +3553,80 @@ public class ConnectivityServiceTest { } } + private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback { + + public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = SocketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue + && this.callbackType == ((CallbackValue) o).callbackType + && this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, + error); + } + } + + private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) { + try { + assertEquals( + callbackValue, + mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); + } + } + + public void expectStarted() { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } + } + private Network connectKeepaliveNetwork(LinkProperties lp) { // Ensure the network is disconnected before we do anything. if (mWiFiNetworkAgent != null) { @@ -3689,6 +3774,145 @@ public class ConnectivityServiceTest { } @Test + public void testNattSocketKeepalives() throws Exception { + // TODO: 1. Move this outside of ConnectivityServiceTest. + // 2. Add helper function to test against newSingleThreadExecutor as well as inline + // executor. + // 3. Make test to verify that Nat-T keepalive socket is created by IpSecService. + final int srcPort = 12345; + final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + final int validKaInterval = 15; + final int invalidKaInterval = 9; + + final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + + final Executor executor = Executors.newSingleThreadExecutor(); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(); + SocketKeepalive ka; + + // Attempt to start keepalives with invalid parameters and check for errors. + // Invalid network. + ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + + // Invalid interval. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(invalidKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL); + + // Invalid destination. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // Invalid source; + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // NAT-T is only supported for IPv4. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // Sanity check before testing started keepalive. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + + // Check that a started keepalive is stopped correctly when the network disconnects. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + waitForIdle(); + final Network myNetAlias = myNet; + assertNull(mCm.getNetworkCapabilities(myNetAlias)); + ka.stop(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + + // Check things work as expected when the keepalive is stopped and the network disconnects. + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + ka.stop(); + mWiFiNetworkAgent.disconnect(); + waitFor(mWiFiNetworkAgent.getDisconnectedCV()); + waitForIdle(); + callback.expectStopped(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback); + ka.start(validKaInterval); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789); + TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(); + SocketKeepalive ka2 = + mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2); + ka2.start(validKaInterval); + callback2.expectStarted(); + + ka.stop(); + callback.expectStopped(); + + ka2.stop(); + callback2.expectStopped(); + } + + @Test public void testGetCaptivePortalServerUrl() throws Exception { String url = mCm.getCaptivePortalServerUrl(); assertEquals("http://connectivitycheck.gstatic.com/generate_204", url); @@ -4404,8 +4628,7 @@ public class ConnectivityServiceTest { mMockVpn.setUids(ranges); // VPN networks do not satisfy the default request and are automatically validated // by NetworkMonitor - assertFalse(NetworkMonitorUtils.isValidationRequired( - mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities)); + assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities)); vpnNetworkAgent.setNetworkValid(); vpnNetworkAgent.connect(false); diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java index c748d0f5f743..f2ecef95b599 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.doReturn; import android.content.Context; import android.net.ipmemorystore.Blob; import android.net.ipmemorystore.IOnBlobRetrievedListener; +import android.net.ipmemorystore.IOnL2KeyResponseListener; import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; import android.net.ipmemorystore.IOnSameNetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; @@ -67,7 +68,14 @@ public class IpMemoryStoreServiceTest { private static final String TEST_CLIENT_ID = "testClientId"; private static final String TEST_DATA_NAME = "testData"; - private static final String[] FAKE_KEYS = { "fakeKey1", "fakeKey2", "fakeKey3", "fakeKey4" }; + private static final int FAKE_KEY_COUNT = 20; + private static final String[] FAKE_KEYS; + static { + FAKE_KEYS = new String[FAKE_KEY_COUNT]; + for (int i = 0; i < FAKE_KEYS.length; ++i) { + FAKE_KEYS[i] = "fakeKey" + i; + } + } @Mock private Context mMockContext; @@ -170,6 +178,25 @@ public class IpMemoryStoreServiceTest { }; } + /** Helper method to make an IOnL2KeyResponseListener */ + private interface OnL2KeyResponseListener { + void onL2KeyResponse(Status status, String key); + } + private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) { + return new IOnL2KeyResponseListener() { + @Override + public void onL2KeyResponse(final StatusParcelable status, final String key) + throws RemoteException { + functor.onL2KeyResponse(new Status(status), key); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + // Helper method to factorize some boilerplate private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) { final CountDownLatch latch = new CountDownLatch(1); @@ -195,12 +222,9 @@ public class IpMemoryStoreServiceTest { } @Test - public void testNetworkAttributes() { + public void testNetworkAttributes() throws UnknownHostException { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); - try { - na.setAssignedV4Address( - (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4})); - } catch (UnknownHostException e) { /* Can't happen */ } + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); na.setGroupHint("hint1"); na.setMtu(219); final String l2Key = FAKE_KEYS[0]; @@ -218,10 +242,8 @@ public class IpMemoryStoreServiceTest { }))); final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder(); - try { - na.setDnsAddresses(Arrays.asList( - new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); - } catch (UnknownHostException e) { /* Still can't happen */ } + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); final NetworkAttributes attributes2 = na2.build(); storeAttributes("Did not complete storing attributes 2", l2Key, attributes2); @@ -333,8 +355,93 @@ public class IpMemoryStoreServiceTest { } @Test - public void testFindL2Key() { - // TODO : implement this + public void testFindL2Key() throws UnknownHostException { + final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); + na.setGroupHint("hint0"); + storeAttributes(FAKE_KEYS[0], na.build()); + + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")})); + na.setMtu(219); + storeAttributes(FAKE_KEYS[1], na.build()); + na.setMtu(null); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); + na.setGroupHint("hint1"); + storeAttributes(FAKE_KEYS[2], na.build()); + na.setMtu(219); + storeAttributes(FAKE_KEYS[3], na.build()); + na.setMtu(240); + storeAttributes(FAKE_KEYS[4], na.build()); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8")); + storeAttributes(FAKE_KEYS[5], na.build()); + + // Matches key 5 exactly + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[5], key); + }))); + + // MTU matches key 4 but v4 address matches key 5. The latter is stronger. + na.setMtu(240); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[5], key); + }))); + + // Closest to key 3 (indeed, identical) + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setMtu(219); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + }))); + + // Group hint alone must not be strong enough to override the rest + na.setGroupHint("hint0"); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + }))); + + // Still closest to key 3, though confidence is lower + na.setGroupHint("hint1"); + na.setDnsAddresses(null); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + }))); + + // But changing the MTU makes this closer to key 4 + na.setMtu(240); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[4], key); + }))); + + // MTU alone not strong enough to make this group-close + na.setGroupHint(null); + na.setDnsAddresses(null); + na.setAssignedV4Address(null); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertNull(key); + }))); } private void assertNetworksSameness(final String key1, final String key2, final int sameness) { @@ -349,7 +456,7 @@ public class IpMemoryStoreServiceTest { @Test public void testIsSameNetwork() throws UnknownHostException { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); - na.setAssignedV4Address((Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4})); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); na.setGroupHint("hint1"); na.setMtu(219); na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6"))); diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java new file mode 100644 index 000000000000..d0350aff5ef5 --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.filters; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + +/** + * JUnit filter to select tests. + * + * <p>This filter selects tests specified by package name, class name, and method name. With this + * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the + * restriction that prevents using the package and the class options can be mitigated. + * + * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2. \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * Note that the ending {@code .} in package name is mandatory. + * + * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA,package2.ClassB \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * <p><b>Select out test methods from Java classes:</b> + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * Those options can be used simultaneously. For example + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * will select out all tests in package1, all tests in classA, and ClassB#methodZ test. + * + * <p>Note that when this option is specified with either {@code -e package} or {@code -e class} + * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage}, + * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected + * with this SelectTest option. + * + * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely + * logs to logcat while parsing {@code -e selectTest} option. + */ +public class SelectTest extends Filter { + + private static final String TAG = SelectTest.class.getSimpleName(); + + @VisibleForTesting + static final String OPTION_SELECT_TEST = "selectTest"; + @VisibleForTesting + static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose"; + + private static final String ARGUMENT_ITEM_SEPARATOR = ","; + private static final String PACKAGE_NAME_SEPARATOR = "."; + private static final String METHOD_SEPARATOR = "#"; + + @Nullable + private final PackageSet mPackageSet; + + /** + * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}. + * + * @param testArgs instrumentation test arguments. + */ + public SelectTest(@NonNull Bundle testArgs) { + mPackageSet = parseSelectTest(testArgs); + } + + @Override + public boolean shouldRun(Description description) { + if (mPackageSet == null) { + // Accept all tests because this filter is disabled. + return true; + } + String testClassName = description.getClassName(); + String testMethodName = description.getMethodName(); + return mPackageSet.accept(testClassName, testMethodName); + } + + @Override + public String describe() { + return OPTION_SELECT_TEST + "=" + mPackageSet; + } + + /** + * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}. + * + * <p>This method is intended to be used at constructor of extended {@link Filter} class. + * + * @param testArgs instrumentation test arguments. + * @param selectTests array of class name to be selected to run. + * @return modified instrumentation test arguments. + */ + @NonNull + protected static Bundle addSelectTest( + @NonNull Bundle testArgs, @NonNull String... selectTests) { + if (selectTests.length == 0) { + return testArgs; + } + testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests))); + return testArgs; + } + + /** + * Parse {@code -e selectTest} argument. + * @param testArgs instrumentation test arguments. + * @return {@link PackageSet} that will filter tests. Returns {@code null} when no + * {@code -e selectTest} option is specified, thus this filter gets disabled. + */ + @Nullable + private static PackageSet parseSelectTest(Bundle testArgs) { + final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST); + if (selectTestArgs == null) { + Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified"); + return null; + } + + final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE)); + final PackageSet packageSet = new PackageSet(verbose); + for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) { + packageSet.add(selectTestArg); + } + return packageSet; + } + + private static String getPackageName(String selectTestArg) { + int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR); + return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos); + } + + @Nullable + private static String getClassName(String selectTestArg) { + if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) { + return null; + } + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos); + } + + @Nullable + private static String getMethodName(String selectTestArg) { + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1); + } + + /** Package level filter */ + private static class PackageSet { + private final boolean mVerbose; + /** + * Java package name to {@link ClassSet} map. To represent package filtering, a map value + * can be {@code null}. + */ + private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>(); + + PackageSet(boolean verbose) { + mVerbose = verbose; + } + + void add(final String selectTestArg) { + final String packageName = getPackageName(selectTestArg); + final String className = getClassName(selectTestArg); + + if (className == null) { + ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering. + if (mVerbose) { + logging("Select package " + selectTestArg, classSet != null, + "; supersede " + classSet); + } + return; + } + + ClassSet classSet = mClassSetMap.get(packageName); + if (classSet == null) { + if (mClassSetMap.containsKey(packageName)) { + if (mVerbose) { + logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true, + " ignore " + selectTestArg); + } + return; + } + classSet = new ClassSet(mVerbose); + mClassSetMap.put(packageName, classSet); + } + classSet.add(selectTestArg); + } + + boolean accept(String className, @Nullable String methodName) { + String packageName = getPackageName(className); + if (!mClassSetMap.containsKey(packageName)) { + return false; + } + ClassSet classSet = mClassSetMap.get(packageName); + return classSet == null || classSet.accept(className, methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String packageName : mClassSetMap.keySet()) { + ClassSet classSet = mClassSetMap.get(packageName); + joiner.add(classSet == null + ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString()); + } + return joiner.toString(); + } + } + + /** Class level filter */ + private static class ClassSet { + private final boolean mVerbose; + /** + * Java class name to set of method names map. To represent class filtering, a map value + * can be {@code null}. + */ + private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>(); + + ClassSet(boolean verbose) { + mVerbose = verbose; + } + + void add(String selectTestArg) { + final String className = getClassName(selectTestArg); + final String methodName = getMethodName(selectTestArg); + + if (methodName == null) { + Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering. + if (mVerbose) { + logging("Select class " + selectTestArg, methodSet != null, + "; supersede " + toString(className, methodSet)); + } + return; + } + + Set<String> methodSet = mMethodSetMap.get(className); + if (methodSet == null) { + if (mMethodSetMap.containsKey(className)) { + if (mVerbose) { + logging("Select class " + className, true, "; ignore " + selectTestArg); + } + return; + } + methodSet = new LinkedHashSet<>(); + mMethodSetMap.put(className, methodSet); + } + + methodSet.add(methodName); + if (mVerbose) { + logging("Select method " + selectTestArg, false, null); + } + } + + boolean accept(String className, @Nullable String methodName) { + if (!mMethodSetMap.containsKey(className)) { + return false; + } + Set<String> methodSet = mMethodSetMap.get(className); + return methodName == null || methodSet == null || methodSet.contains(methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String className : mMethodSetMap.keySet()) { + joiner.add(toString(className, mMethodSetMap.get(className))); + } + return joiner.toString(); + } + + private static String toString(String className, @Nullable Set<String> methodSet) { + if (methodSet == null) { + return className; + } + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String methodName : methodSet) { + joiner.add(className + METHOD_SEPARATOR + methodName); + } + return joiner.toString(); + } + } + + private static void logging(String infoLog, boolean isWarning, String warningLog) { + if (isWarning) { + Log.w(TAG, infoLog + warningLog); + } else { + Log.i(TAG, infoLog); + } + } + + private static String join(Collection<String> list) { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String text : list) { + joiner.add(text); + } + return joiner.toString(); + } +} diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java new file mode 100644 index 000000000000..163b00abafcd --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.filters; + +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST; +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Bundle; +import android.util.ArraySet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + +public class SelectTestTests { + + private static final String PACKAGE_A = "packageA."; + private static final String PACKAGE_B = "packageB."; + private static final String PACKAGE_C = "packageC."; + private static final String CLASS_A1 = PACKAGE_A + "Class1"; + private static final String CLASS_A2 = PACKAGE_A + "Class2"; + private static final String CLASS_B3 = PACKAGE_B + "Class3"; + private static final String CLASS_B4 = PACKAGE_B + "Class4"; + private static final String CLASS_C5 = PACKAGE_C + "Class5"; + private static final String CLASS_C6 = PACKAGE_C + "Class6"; + private static final String METHOD_A1K = CLASS_A1 + "#methodK"; + private static final String METHOD_A1L = CLASS_A1 + "#methodL"; + private static final String METHOD_A2M = CLASS_A2 + "#methodM"; + private static final String METHOD_A2N = CLASS_A2 + "#methodN"; + private static final String METHOD_B3P = CLASS_B3 + "#methodP"; + private static final String METHOD_B3Q = CLASS_B3 + "#methodQ"; + private static final String METHOD_B4R = CLASS_B4 + "#methodR"; + private static final String METHOD_B4S = CLASS_B4 + "#methodS"; + private static final String METHOD_C5W = CLASS_C5 + "#methodW"; + private static final String METHOD_C5X = CLASS_C5 + "#methodX"; + private static final String METHOD_C6Y = CLASS_C6 + "#methodY"; + private static final String METHOD_C6Z = CLASS_C6 + "#methodZ"; + + private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K); + private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L); + private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M); + private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N); + private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P); + private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q); + private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R); + private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S); + private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W); + private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X); + private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y); + private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z); + private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L); + private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N); + private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q); + private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S); + private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X); + private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z); + private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2); + private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4); + private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6); + private static final Set<Description> TEST_ALL = + merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C); + + private SelectTestBuilder mBuilder; + + @Before + public void setUp() { + mBuilder = new SelectTestBuilder(); + } + + private static class SelectTestBuilder { + private final Bundle mTestArgs = new Bundle(); + + Filter build() { + mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString()); + return new SelectTest(mTestArgs); + } + + SelectTestBuilder withSelectTest(String... selectTestArgs) { + putTestOption(OPTION_SELECT_TEST, selectTestArgs); + return this; + } + + private void putTestOption(String option, String... args) { + if (args.length > 0) { + StringJoiner joiner = new StringJoiner(","); + for (String arg : args) { + joiner.add(arg); + } + mTestArgs.putString(option, joiner.toString()); + } + } + } + + private static Set<Description> methodTest(String testName) { + int methodSep = testName.indexOf("#"); + String className = testName.substring(0, methodSep); + String methodName = testName.substring(methodSep + 1); + final Set<Description> tests = new ArraySet<>(); + tests.add(Description.createSuiteDescription(className)); + tests.add(Description.createTestDescription(className, methodName)); + return Collections.unmodifiableSet(tests); + } + + @SafeVarargs + private static Set<Description> merge(Set<Description>... testSpecs) { + final Set<Description> merged = new LinkedHashSet<>(); + for (Set<Description> testSet : testSpecs) { + merged.addAll(testSet); + } + return Collections.unmodifiableSet(merged); + } + + @SafeVarargs + private static void acceptTests(Filter filter, Set<Description>... testSpecs) { + final Set<Description> accepts = merge(testSpecs); + for (Description test : TEST_ALL) { + if (accepts.contains(test)) { + assertTrue("accept " + test, filter.shouldRun(test)); + } else { + assertFalse("reject " + test, filter.shouldRun(test)); + } + } + } + + @Test + public void testFilterDisabled() { + final Filter filter = mBuilder.build(); + acceptTests(filter, TEST_ALL); + } + + @Test + public void testSelectPackage() { + final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } + + @Test + public void testSelectClass() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3); + } + + @Test + public void testSelectMethod() { + final Filter filter = mBuilder + .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndPackage() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5); + } + + @Test + public void testSelectMethodAndPackage() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W); + } + + @Test + public void testSelectMethodAndClass() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndSamePackage() { + final Filter filter = mBuilder.withSelectTest( + CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndSameClass() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R); + } + + @Test + public void testSelectMethodAndSamePackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndClassAndPackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } +} diff --git a/tools/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp index 1aca3edfab88..0e33fddcde07 100644 --- a/tools/processors/unsupportedappusage/Android.bp +++ b/tools/processors/unsupportedappusage/Android.bp @@ -1,6 +1,8 @@ -java_library_host { +java_plugin { name: "unsupportedappusage-annotation-processor", + processor_class: "android.processor.unsupportedappusage.UnsupportedAppUsageProcessor", + java_resources: [ "META-INF/**/*", ], |