diff options
Diffstat (limited to 'services')
14 files changed, 1219 insertions, 27 deletions
diff --git a/services/core/Android.mk b/services/core/Android.mk index 64b6134..666f2ff 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -9,7 +9,7 @@ LOCAL_SRC_FILES += \ java/com/android/server/EventLogTags.logtags \ java/com/android/server/am/EventLogTags.logtags -LOCAL_JAVA_LIBRARIES := telephony-common +LOCAL_JAVA_LIBRARIES := services.net telephony-common LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index b3b4651..2eeaec9 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -120,6 +120,7 @@ public final class BatteryService extends SystemService { private int mLastBatteryVoltage; private int mLastBatteryTemperature; private boolean mLastBatteryLevelCritical; + private int mLastMaxChargingCurrent; private int mInvalidCharger; private int mLastInvalidCharger; @@ -323,6 +324,7 @@ public final class BatteryService extends SystemService { + "chargerAcOnline=" + mBatteryProps.chargerAcOnline + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline + + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent + ", batteryStatus=" + mBatteryProps.batteryStatus + ", batteryHealth=" + mBatteryProps.batteryHealth + ", batteryPresent=" + mBatteryProps.batteryPresent @@ -353,6 +355,7 @@ public final class BatteryService extends SystemService { mPlugType != mLastPlugType || mBatteryProps.batteryVoltage != mLastBatteryVoltage || mBatteryProps.batteryTemperature != mLastBatteryTemperature || + mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent || mInvalidCharger != mLastInvalidCharger)) { if (mPlugType != mLastPlugType) { @@ -479,6 +482,7 @@ public final class BatteryService extends SystemService { mLastPlugType = mPlugType; mLastBatteryVoltage = mBatteryProps.batteryVoltage; mLastBatteryTemperature = mBatteryProps.batteryTemperature; + mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent; mLastBatteryLevelCritical = mBatteryLevelCritical; mLastInvalidCharger = mInvalidCharger; } @@ -503,17 +507,21 @@ public final class BatteryService extends SystemService { intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature); intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); + intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent); if (DEBUG) { Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryProps.batteryLevel + ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus + - ", health:" + mBatteryProps.batteryHealth + ", present:" + mBatteryProps.batteryPresent + + ", health:" + mBatteryProps.batteryHealth + + ", present:" + mBatteryProps.batteryPresent + ", voltage: " + mBatteryProps.batteryVoltage + ", temperature: " + mBatteryProps.batteryTemperature + ", technology: " + mBatteryProps.batteryTechnology + - ", AC powered:" + mBatteryProps.chargerAcOnline + ", USB powered:" + mBatteryProps.chargerUsbOnline + + ", AC powered:" + mBatteryProps.chargerAcOnline + + ", USB powered:" + mBatteryProps.chargerUsbOnline + ", Wireless powered:" + mBatteryProps.chargerWirelessOnline + - ", icon:" + icon + ", invalid charger:" + mInvalidCharger); + ", icon:" + icon + ", invalid charger:" + mInvalidCharger + + ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent); } mHandler.post(new Runnable() { @@ -618,6 +626,7 @@ public final class BatteryService extends SystemService { pw.println(" AC powered: " + mBatteryProps.chargerAcOnline); pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline); pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline); + pw.println(" Max charging current: " + mBatteryProps.maxChargingCurrent); pw.println(" status: " + mBatteryProps.batteryStatus); pw.println(" health: " + mBatteryProps.batteryHealth); pw.println(" present: " + mBatteryProps.batteryPresent); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 0802d30..028460c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -47,6 +47,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.PacketKeepalive; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.INetworkPolicyListener; @@ -117,6 +118,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; +import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; @@ -148,6 +150,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -399,6 +403,8 @@ public class ConnectivityService extends IConnectivityManager.Stub TelephonyManager mTelephonyManager; + private KeepaliveTracker mKeepaliveTracker; + // sequence number for Networks; keep in sync with system/netd/NetworkController.cpp private final static int MIN_NET_ID = 100; // some reserved marks private final static int MAX_NET_ID = 65535; @@ -764,6 +770,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + + mKeepaliveTracker = new KeepaliveTracker(mHandler); } private NetworkRequest createInternetRequestForTransport(int transportType) { @@ -1449,6 +1457,10 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private void enforceKeepalivePermission() { + mContext.enforceCallingPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); + } + public void sendConnectedBroadcast(NetworkInfo info) { enforceConnectivityInternalPermission(); sendGeneralBroadcast(info, CONNECTIVITY_ACTION); @@ -1840,10 +1852,13 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(", last requested never"); } } - pw.println(); + pw.println(); mTethering.dump(fd, pw, args); + pw.println(); + mKeepaliveTracker.dump(pw); + if (mInetLog != null && mInetLog.size() > 0) { pw.println(); pw.println("Inet condition reports:"); @@ -1921,7 +1936,12 @@ public class ConnectivityService extends IConnectivityManager.Stub (NetworkCapabilities)msg.obj; if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) || networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { - Slog.wtf(TAG, "BUG: " + nai + " has stateful capability."); + Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability."); + } + if (nai.created && !nai.networkCapabilities.equalImmutableCapabilities( + networkCapabilities)) { + Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: " + + nai.networkCapabilities + " -> " + networkCapabilities); } updateCapabilities(nai, networkCapabilities); } @@ -2005,6 +2025,15 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkMisc.acceptUnvalidated = (boolean) msg.obj; break; } + case NetworkAgent.EVENT_PACKET_KEEPALIVE: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_PACKET_KEEPALIVE from unknown NetworkAgent"); + break; + } + mKeepaliveTracker.handleEventPacketKeepalive(nai, msg); + break; + } case NetworkMonitor.EVENT_NETWORK_TESTED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; if (isLiveNetworkAgent(nai, "EVENT_NETWORK_TESTED")) { @@ -2147,6 +2176,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // sending all CALLBACK_LOST messages (for requests, not listens) at the end // of rematchAllNetworksAndRequests notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST); + mKeepaliveTracker.handleStopAllKeepalives(nai, + ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK); nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED); mNetworkAgentInfos.remove(msg.replyTo); updateClat(null, nai.linkProperties, nai); @@ -2225,6 +2256,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleRegisterNetworkRequest(NetworkRequestInfo nri) { mNetworkRequests.put(nri.request, nri); mNetworkRequestInfoLogs.log("REGISTER " + nri); + if (!nri.isRequest) { + for (NetworkAgentInfo network : mNetworkAgentInfos.values()) { + if (network.satisfiesImmutableCapabilitiesOf(nri.request)) { + updateSignalStrengthThresholds(network); + } + } + } rematchAllNetworksAndRequests(null, 0); if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) { sendUpdatedScoreToFactories(nri.request, 0); @@ -2338,6 +2376,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // if this listen request applies and remove it. for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkRequests.remove(nri.request.requestId); + if (nai.satisfiesImmutableCapabilitiesOf(nri.request)) { + updateSignalStrengthThresholds(nai); + } } } callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED); @@ -2504,6 +2545,19 @@ public class ConnectivityService extends IConnectivityManager.Stub handleMobileDataAlwaysOn(); break; } + // Sent by KeepaliveTracker to process an app request on the state machine thread. + case NetworkAgent.CMD_START_PACKET_KEEPALIVE: { + mKeepaliveTracker.handleStartKeepalive(msg); + break; + } + // Sent by KeepaliveTracker to process an app request on the state machine thread. + case NetworkAgent.CMD_STOP_PACKET_KEEPALIVE: { + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj); + int slot = msg.arg1; + int reason = msg.arg2; + mKeepaliveTracker.handleStopKeepalive(nai, slot, reason); + break; + } case EVENT_SYSTEM_READY: { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkMonitor.systemReady = true; @@ -3553,15 +3607,32 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void ensureImmutableCapabilities(NetworkCapabilities networkCapabilities) { - if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { - throw new IllegalArgumentException( - "Cannot request network with NET_CAPABILITY_VALIDATED"); + private void ensureRequestableCapabilities(NetworkCapabilities networkCapabilities) { + final String badCapability = networkCapabilities.describeFirstNonRequestableCapability(); + if (badCapability != null) { + throw new IllegalArgumentException("Cannot request network with " + badCapability); } - if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) { - throw new IllegalArgumentException( - "Cannot request network with NET_CAPABILITY_CAPTIVE_PORTAL"); + } + + private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) { + final SortedSet<Integer> thresholds = new TreeSet(); + synchronized (nai) { + for (NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.request.networkCapabilities.hasSignalStrength() && + nai.satisfiesImmutableCapabilitiesOf(nri.request)) { + thresholds.add(nri.request.networkCapabilities.getSignalStrength()); + } + } } + return new ArrayList<Integer>(thresholds); + } + + private void updateSignalStrengthThresholds(NetworkAgentInfo nai) { + Bundle thresholds = new Bundle(); + thresholds.putIntegerArrayList("thresholds", getSignalStrengthThresholds(nai)); + nai.asyncChannel.sendMessage( + android.net.NetworkAgent.CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, + 0, 0, thresholds); } @Override @@ -3570,7 +3641,7 @@ public class ConnectivityService extends IConnectivityManager.Stub networkCapabilities = new NetworkCapabilities(networkCapabilities); enforceNetworkRequestPermissions(networkCapabilities); enforceMeteredApnPolicy(networkCapabilities); - ensureImmutableCapabilities(networkCapabilities); + ensureRequestableCapabilities(networkCapabilities); if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) { throw new IllegalArgumentException("Bad timeout specified"); @@ -3639,7 +3710,7 @@ public class ConnectivityService extends IConnectivityManager.Stub networkCapabilities = new NetworkCapabilities(networkCapabilities); enforceNetworkRequestPermissions(networkCapabilities); enforceMeteredApnPolicy(networkCapabilities); - ensureImmutableCapabilities(networkCapabilities); + ensureRequestableCapabilities(networkCapabilities); NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId()); @@ -3865,6 +3936,8 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyIfacesChanged(); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED); } + + mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent); } private void updateClat(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) { @@ -4531,6 +4604,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: support proxy per network. } + // Whether a particular NetworkRequest listen should cause signal strength thresholds to + // be communicated to a particular NetworkAgent depends only on the network's immutable, + // capabilities, so it only needs to be done once on initial connect, not every time the + // network's capabilities change. Note that we do this before rematching the network, + // so we could decide to tear it down immediately afterwards. That's fine though - on + // disconnection NetworkAgents should stop any signal strength monitoring they have been + // doing. + updateSignalStrengthThresholds(networkAgent); + // Consider network even though it is not yet validated. rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP); @@ -4710,6 +4792,22 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void startNattKeepalive(Network network, int intervalSeconds, Messenger messenger, + IBinder binder, String srcAddr, int srcPort, String dstAddr) { + enforceKeepalivePermission(); + mKeepaliveTracker.startNattKeepalive( + getNetworkAgentInfoForNetwork(network), + intervalSeconds, messenger, binder, + srcAddr, srcPort, dstAddr, ConnectivityManager.PacketKeepalive.NATT_PORT); + } + + @Override + public void stopKeepalive(Network network, int slot) { + mHandler.sendMessage(mHandler.obtainMessage( + NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network)); + } + + @Override public void factoryReset() { enforceConnectivityInternalPermission(); diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java new file mode 100644 index 0000000..e17ff5c --- /dev/null +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -0,0 +1,197 @@ +/* + * 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 com.android.server; + +import android.app.KeyguardManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.PowerManager; +import android.os.Vibrator; +import android.os.PowerManager.WakeLock; +import android.os.SystemProperties; +import android.provider.MediaStore; +import android.provider.Settings; +import android.util.Slog; + +/** + * The service that listens for gestures detected in sensor firmware and starts the intent + * accordingly. + * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be + * added.</p> + * @hide + */ +class GestureLauncherService extends SystemService { + private static final boolean DBG = false; + private static final String TAG = "GestureLauncherService"; + + /** The listener that receives the gesture event. */ + private final GestureEventListener mGestureListener = new GestureEventListener(); + + private Sensor mCameraLaunchSensor; + private Vibrator mVibrator; + private KeyguardManager mKeyGuard; + private Context mContext; + + /** The wake lock held when a gesture is detected. */ + private WakeLock mWakeLock; + + public GestureLauncherService(Context context) { + super(context); + mContext = context; + } + + public void onStart() { + // Nothing to publish. + } + + public void onBootPhase(int phase) { + if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + Resources resources = mContext.getResources(); + if (!isGestureLauncherEnabled(resources)) { + if (DBG) Slog.d(TAG, "Gesture launcher is disabled in system properties."); + return; + } + + mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + mKeyGuard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + PowerManager powerManager = (PowerManager) mContext.getSystemService( + Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock( + PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, + "GestureLauncherService"); + if (isCameraLaunchEnabled(resources)) { + registerCameraLaunchGesture(resources); + } + } + } + + /** + * Registers for the camera launch gesture. + */ + private void registerCameraLaunchGesture(Resources resources) { + SensorManager sensorManager = (SensorManager) mContext.getSystemService( + Context.SENSOR_SERVICE); + int cameraLaunchGestureId = resources.getInteger( + com.android.internal.R.integer.config_cameraLaunchGestureSensorType); + if (cameraLaunchGestureId != -1) { + boolean registered = false; + String sensorName = resources.getString( + com.android.internal.R.string.config_cameraLaunchGestureSensorStringType); + mCameraLaunchSensor = sensorManager.getDefaultSensor( + cameraLaunchGestureId, + true /*wakeUp*/); + + // Compare the camera gesture string type to that in the resource file to make + // sure we are registering the correct sensor. This is redundant check, it + // makes the code more robust. + if (mCameraLaunchSensor != null) { + if (sensorName.equals(mCameraLaunchSensor.getStringType())) { + registered = sensorManager.registerListener(mGestureListener, + mCameraLaunchSensor, 0); + } else { + String message = String.format("Wrong configuration. Sensor type and sensor " + + "string type don't match: %s in resources, %s in the sensor.", + sensorName, mCameraLaunchSensor.getStringType()); + throw new RuntimeException(message); + } + } + if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + registered); + } else { + if (DBG) Slog.d(TAG, "Camera launch sensor is not specified."); + } + } + + /** + * Whether to enable the camera launch gesture. + */ + public static boolean isCameraLaunchEnabled(Resources resources) { + boolean configSet = resources.getInteger( + com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1; + return configSet && + !SystemProperties.getBoolean("gesture.disable_camera_launch", false); + } + + /** + * Whether GestureLauncherService should be enabled according to system properties. + */ + public static boolean isGestureLauncherEnabled(Resources resources) { + // For now, the only supported gesture is camera launch gesture, so whether to enable this + // service equals to isCameraLaunchEnabled(); + return isCameraLaunchEnabled(resources); + } + + private final class GestureEventListener implements SensorEventListener { + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor == mCameraLaunchSensor) { + handleCameraLaunchGesture(); + return; + } + } + + private void handleCameraLaunchGesture() { + if (DBG) Slog.d(TAG, "Received a camera launch event."); + boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; + if (!userSetupComplete) { + if (DBG) Slog.d(TAG, String.format( + "userSetupComplete = %s, ignoring camera launch gesture.", + userSetupComplete)); + return; + } + if (DBG) Slog.d(TAG, String.format( + "userSetupComplete = %s, performing camera launch gesture.", + userSetupComplete)); + boolean locked = mKeyGuard != null && mKeyGuard.inKeyguardRestrictedInputMode(); + String action = locked + ? MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE + : MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA; + Intent intent = new Intent(action); + PackageManager pm = mContext.getPackageManager(); + ResolveInfo componentInfo = pm.resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY); + if (componentInfo == null) { + if (DBG) Slog.d(TAG, "Couldn't find an app to process the camera intent."); + return; + } + + if (mVibrator != null && mVibrator.hasVibrator()) { + mVibrator.vibrate(1000L); + } + // Turn on the screen before the camera launches. + mWakeLock.acquire(500L); + intent.setComponent(new ComponentName(componentInfo.activityInfo.packageName, + componentInfo.activityInfo.name)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + mWakeLock.release(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Ignored. + } + } +} diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 53e8d14..7fa1d09 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -2576,6 +2576,63 @@ class MountService extends IMountService.Stub } @Override + public void createNewUserDir(int userHandle, String path) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only SYSTEM_UID can create user directories"); + } + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "Creating new user dir"); + } + + try { + NativeDaemonEvent event = mCryptConnector.execute( + "cryptfs", "createnewuserdir", userHandle, path); + if (!"0".equals(event.getMessage())) { + String error = "createnewuserdir sent unexpected message: " + + event.getMessage(); + Slog.e(TAG, error); + // ext4enc:TODO is this the right exception? + throw new RuntimeException(error); + } + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "createnewuserdir threw exception", e); + throw new RuntimeException("createnewuserdir threw exception", e); + } + } + + // ext4enc:TODO duplication between this and createNewUserDir is nasty + @Override + public void deleteUserKey(int userHandle) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only SYSTEM_UID can delete user keys"); + } + + waitForReady(); + + if (DEBUG_EVENTS) { + Slog.i(TAG, "Deleting user key"); + } + + try { + NativeDaemonEvent event = mCryptConnector.execute( + "cryptfs", "deleteuserkey", userHandle); + if (!"0".equals(event.getMessage())) { + String error = "deleteuserkey sent unexpected message: " + + event.getMessage(); + Slog.e(TAG, error); + // ext4enc:TODO is this the right exception? + throw new RuntimeException(error); + } + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "deleteuserkey threw exception", e); + throw new RuntimeException("deleteuserkey threw exception", e); + } + } + + @Override public int mkdirs(String callingPkg, String appPath) { final int userId = UserHandle.getUserId(Binder.getCallingUid()); final UserEnvironment userEnv = new UserEnvironment(userId); diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java new file mode 100644 index 0000000..64b9399 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java @@ -0,0 +1,136 @@ +/* + * 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 com.android.server.connectivity; + +import android.system.OsConstants; +import android.net.ConnectivityManager; +import android.net.NetworkUtils; +import android.net.util.IpUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static android.net.ConnectivityManager.PacketKeepalive.*; + +/** + * Represents the actual packets that are sent by the + * {@link android.net.ConnectivityManager.PacketKeepalive} API. + * + * @hide + */ +public class KeepalivePacketData { + /** Protocol of the packet to send; one of the OsConstants.ETH_P_* values. */ + public final int protocol; + + /** Source IP address */ + public final InetAddress srcAddress; + + /** Destination IP address */ + public final InetAddress dstAddress; + + /** Source port */ + public final int srcPort; + + /** Destination port */ + public final int dstPort; + + /** Destination MAC address. Can change if routing changes. */ + public byte[] dstMac; + + /** Packet data. A raw byte string of packet data, not including the link-layer header. */ + public final byte[] data; + + private static final int IPV4_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + + protected KeepalivePacketData(InetAddress srcAddress, int srcPort, + InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException { + this.srcAddress = srcAddress; + this.dstAddress = dstAddress; + this.srcPort = srcPort; + this.dstPort = dstPort; + this.data = data; + + // Check we have two IP addresses of the same family. + if (srcAddress == null || dstAddress == null || + !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) { + } + + // Set the protocol. + if (this.dstAddress instanceof Inet4Address) { + this.protocol = OsConstants.ETH_P_IP; + } else if (this.dstAddress instanceof Inet6Address) { + this.protocol = OsConstants.ETH_P_IPV6; + } else { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + // Check the ports. + if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + } + + public static class InvalidPacketException extends Exception { + final public int error; + public InvalidPacketException(int error) { + this.error = error; + } + } + + /** + * Creates an IPsec NAT-T keepalive packet with the specified parameters. + */ + public static KeepalivePacketData nattKeepalivePacket( + InetAddress srcAddress, int srcPort, + InetAddress dstAddress, int dstPort) throws InvalidPacketException { + + if (!(srcAddress instanceof Inet4Address)) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + if (dstPort != NATT_PORT) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + + int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort((short) 0x4500); // IP version and TOS + buf.putShort((short) length); + buf.putInt(0); // ID, flags, offset + buf.put((byte) 64); // TTL + buf.put((byte) OsConstants.IPPROTO_UDP); + int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum + buf.put(srcAddress.getAddress()); + buf.put(dstAddress.getAddress()); + buf.putShort((short) srcPort); + buf.putShort((short) dstPort); + buf.putShort((short) (length - 20)); // UDP length + int udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum + buf.put((byte) 0xff); // NAT-T keepalive + buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); + buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); + + return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); + } +} diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java new file mode 100644 index 0000000..c78f347 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -0,0 +1,372 @@ +/* + * 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 com.android.server.connectivity; + +import com.android.internal.util.HexDump; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.KeepalivePacketData; +import com.android.server.connectivity.NetworkAgentInfo; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.PacketKeepalive; +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.Message; +import android.os.Messenger; +import android.os.Process; +import android.os.RemoteException; +import android.system.OsConstants; +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 java.net.InetAddress; +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. + * + * Provides methods to stop and start keepalive requests, and keeps track of keepalives across all + * networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its + * methods must be called only from the ConnectivityService handler thread. + */ +public class KeepaliveTracker { + + private static final String TAG = "KeepaliveTracker"; + private static final boolean DBG = true; + + // TODO: Change this to a system-only permission. + public static final String PERMISSION = android.Manifest.permission.CHANGE_NETWORK_STATE; + + /** Keeps track of keepalive requests. */ + private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives = + new HashMap<> (); + private final Handler mConnectivityServiceHandler; + + public KeepaliveTracker(Handler handler) { + mConnectivityServiceHandler = handler; + } + + /** + * Tracks information about a packet keepalive. + * + * All information about this keepalive is known at construction time except the slot number, + * which is only returned when the hardware has successfully started the keepalive. + */ + class KeepaliveInfo implements IBinder.DeathRecipient { + // Bookkeping data. + private final Messenger mMessenger; + private final IBinder mBinder; + private final int mUid; + private final int mPid; + private final NetworkAgentInfo mNai; + + /** Keepalive slot. A small integer that identifies this keepalive among the ones handled + * by this network. */ + private int mSlot = PacketKeepalive.NO_KEEPALIVE; + + // Packet data. + private final KeepalivePacketData mPacket; + private final int mInterval; + + // Whether the keepalive is started or not. + public boolean isStarted; + + public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai, + KeepalivePacketData packet, int interval) { + mMessenger = messenger; + mBinder = binder; + mPid = Binder.getCallingPid(); + mUid = Binder.getCallingUid(); + + mNai = nai; + mPacket = packet; + mInterval = interval; + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + public NetworkAgentInfo getNai() { + return mNai; + } + + public String toString() { + return new StringBuffer("KeepaliveInfo [") + .append(" network=").append(mNai.network) + .append(" isStarted=").append(isStarted) + .append(" ") + .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort)) + .append("->") + .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)) + .append(" interval=" + mInterval) + .append(" data=" + HexDump.toHexString(mPacket.data)) + .append(" uid=").append(mUid).append(" pid=").append(mPid) + .append(" ]") + .toString(); + } + + /** Sends a message back to the application via its PacketKeepalive.Callback. */ + void notifyMessenger(int slot, int err) { + KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err); + } + + /** Called when the application process is killed. */ + public void binderDied() { + // Not called from ConnectivityService handler thread, so send it a message. + mConnectivityServiceHandler.obtainMessage( + NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, + mSlot, PacketKeepalive.BINDER_DIED, mNai.network).sendToTarget(); + } + + void unlinkDeathRecipient() { + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } + } + + private int checkNetworkConnected() { + if (!mNai.networkInfo.isConnectedOrConnecting()) { + return ERROR_INVALID_NETWORK; + } + return SUCCESS; + } + + private int checkSourceAddress() { + // Check that we have the source address. + for (InetAddress address : mNai.linkProperties.getAddresses()) { + if (address.equals(mPacket.srcAddress)) { + return SUCCESS; + } + } + return ERROR_INVALID_IP_ADDRESS; + } + + private int checkInterval() { + return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL; + } + + private int isValid() { + synchronized (mNai) { + int error = checkInterval(); + if (error == SUCCESS) error = checkNetworkConnected(); + if (error == SUCCESS) error = checkSourceAddress(); + return error; + } + } + + void start(int slot) { + int error = isValid(); + if (error == SUCCESS) { + mSlot = slot; + Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name()); + mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket); + } else { + notifyMessenger(NO_KEEPALIVE, error); + return; + } + } + + void stop(int reason) { + int uid = Binder.getCallingUid(); + if (uid != mUid && uid != Process.SYSTEM_UID) { + if (DBG) { + Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network); + } + } + if (isStarted) { + Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name()); + mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot); + } + notifyMessenger(mSlot, reason); + unlinkDeathRecipient(); + } + } + + void notifyMessenger(Messenger messenger, int slot, int err) { + Message message = Message.obtain(); + message.what = EVENT_PACKET_KEEPALIVE; + message.arg1 = slot; + message.arg2 = err; + message.obj = null; + try { + messenger.send(message); + } catch (RemoteException e) { + // Process died? + } + } + + private int findFirstFreeSlot(NetworkAgentInfo nai) { + HashMap networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives == null) { + networkKeepalives = new HashMap<Integer, KeepaliveInfo>(); + mKeepalives.put(nai, networkKeepalives); + } + + // Find the lowest-numbered free slot. + int slot; + for (slot = 0; slot < networkKeepalives.size(); slot++) { + if (networkKeepalives.get(slot) == null) { + return slot; + } + } + // No free slot, pick one at the end. + + // HACK for broadcom hardware that does not support slot 0! + if (slot == 0) slot = 1; + return slot; + } + + public void handleStartKeepalive(Message message) { + KeepaliveInfo ki = (KeepaliveInfo) message.obj; + NetworkAgentInfo nai = ki.getNai(); + int slot = findFirstFreeSlot(nai); + mKeepalives.get(nai).put(slot, ki); + ki.start(slot); + } + + public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) { + HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives != null) { + for (KeepaliveInfo ki : networkKeepalives.values()) { + ki.stop(reason); + } + networkKeepalives.clear(); + mKeepalives.remove(nai); + } + } + + public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) { + HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives == null) { + Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + nai.name()); + return; + } + KeepaliveInfo ki = networkKeepalives.get(slot); + if (ki == null) { + Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + nai.name()); + return; + } + ki.stop(reason); + networkKeepalives.remove(slot); + if (networkKeepalives.isEmpty()) { + mKeepalives.remove(nai); + } + } + + public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) { + HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); + if (networkKeepalives != null) { + ArrayList<Pair<Integer, Integer>> invalidKeepalives = new ArrayList<>(); + for (int slot : networkKeepalives.keySet()) { + int error = networkKeepalives.get(slot).isValid(); + if (error != SUCCESS) { + invalidKeepalives.add(Pair.create(slot, error)); + } + } + for (Pair<Integer, Integer> slotAndError: invalidKeepalives) { + handleStopKeepalive(nai, slotAndError.first, slotAndError.second); + } + } + } + + public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) { + int slot = message.arg1; + int reason = message.arg2; + + KeepaliveInfo ki = null; + try { + ki = mKeepalives.get(nai).get(slot); + } catch(NullPointerException e) {} + if (ki == null) { + Log.e(TAG, "Event for unknown keepalive " + slot + " on " + nai.name()); + return; + } + + if (reason == SUCCESS && !ki.isStarted) { + // Keepalive successfully started. + if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name()); + ki.isStarted = true; + ki.notifyMessenger(slot, reason); + } else { + // Keepalive successfully stopped, or error. + ki.isStarted = false; + if (reason == SUCCESS) { + if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); + } else { + if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); + } + handleStopKeepalive(nai, slot, reason); + } + } + + public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, + IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { + InetAddress srcAddress, dstAddress; + try { + srcAddress = NetworkUtils.numericToInetAddress(srcAddrString); + dstAddress = NetworkUtils.numericToInetAddress(dstAddrString); + } catch (IllegalArgumentException e) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_IP_ADDRESS); + return; + } + + KeepalivePacketData packet; + try { + packet = KeepalivePacketData.nattKeepalivePacket( + srcAddress, srcPort, dstAddress, NATT_PORT); + } catch (KeepalivePacketData.InvalidPacketException e) { + notifyMessenger(messenger, NO_KEEPALIVE, e.error); + return; + } + KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds); + Log.d(TAG, "Created keepalive: " + ki.toString()); + mConnectivityServiceHandler.obtainMessage( + NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget(); + } + + public void dump(IndentingPrintWriter pw) { + pw.println("Packet keepalives:"); + pw.increaseIndent(); + for (NetworkAgentInfo nai : mKeepalives.keySet()) { + pw.println(nai.name()); + pw.increaseIndent(); + for (int slot : mKeepalives.get(nai).keySet()) { + KeepaliveInfo ki = mKeepalives.get(nai).get(slot); + pw.println(slot + ": " + ki.toString()); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 39333f6..0029279 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -195,6 +195,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities); } + public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) { + return created && + request.networkCapabilities.satisfiedByImmutableNetworkCapabilities( + networkCapabilities); + } + public boolean isVPN() { return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 11e30b5..0a98d41 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -15698,7 +15698,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (userDir.exists()) continue; try { - UserManagerService.prepareUserDirectory(userDir); + UserManagerService.prepareUserDirectory(mContext, volumeUuid, user.id); UserManagerService.enforceSerialNumber(userDir, user.serialNumber); } catch (IOException e) { Log.wtf(TAG, "Failed to create user directory on " + volumeUuid, e); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 82ffa47..5bdad50 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -38,10 +38,13 @@ import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.FileUtils; +import android.os.IBinder; import android.os.Handler; import android.os.Message; import android.os.PatternMatcher; import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 6707562..19c5f0f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1264,7 +1264,7 @@ public class UserManagerService extends IUserManager.Stub { try { final File userDir = Environment.getDataUserDirectory(volumeUuid, userId); - prepareUserDirectory(userDir); + prepareUserDirectory(mContext, volumeUuid, userId); enforceSerialNumber(userDir, userInfo.serialNumber); } catch (IOException e) { Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e); @@ -1472,6 +1472,8 @@ public class UserManagerService extends IUserManager.Stub { } private void removeUserStateLocked(final int userHandle) { + mContext.getSystemService(StorageManager.class) + .deleteUserKey(userHandle); // Cleanup package manager settings mPm.cleanUpUserLILPw(this, userHandle); @@ -1878,16 +1880,10 @@ public class UserManagerService extends IUserManager.Stub { * Create new {@code /data/user/[id]} directory and sets default * permissions. */ - public static void prepareUserDirectory(File file) throws IOException { - if (!file.exists()) { - if (!file.mkdir()) { - throw new IOException("Failed to create " + file); - } - } - if (FileUtils.setPermissions(file.getAbsolutePath(), 0771, Process.SYSTEM_UID, - Process.SYSTEM_UID) != 0) { - throw new IOException("Failed to prepare " + file); - } + public static void prepareUserDirectory(Context context, String volumeUuid, int userId) { + final StorageManager storage = context.getSystemService(StorageManager.class); + final File userDir = Environment.getDataUserDirectory(volumeUuid, userId); + storage.createNewUserDir(userId, userDir); } /** diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 014527b..f1fac9e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -857,6 +857,11 @@ public final class SystemServer { if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_VOICE_RECOGNIZERS)) { mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); } + + if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) { + Slog.i(TAG, "Gesture Launcher Service"); + mSystemServiceManager.startService(GestureLauncherService.class); + } } try { diff --git a/services/net/java/android/net/util/IpUtils.java b/services/net/java/android/net/util/IpUtils.java new file mode 100644 index 0000000..e037c40 --- /dev/null +++ b/services/net/java/android/net/util/IpUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; + +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; + +/** + * @hide + */ +public class IpUtils { + /** + * Converts a signed short value to an unsigned int value. Needed + * because Java does not have unsigned types. + */ + private static int intAbs(short v) { + return v & 0xFFFF; + } + + /** + * Performs an IP checksum (used in IP header and across UDP + * payload) on the specified portion of a ByteBuffer. The seed + * allows the checksum to commence with a specified value. + */ + private static int checksum(ByteBuffer buf, int seed, int start, int end) { + int sum = seed; + final int bufPosition = buf.position(); + + // set position of original ByteBuffer, so that the ShortBuffer + // will be correctly initialized + buf.position(start); + ShortBuffer shortBuf = buf.asShortBuffer(); + + // re-set ByteBuffer position + buf.position(bufPosition); + + final int numShorts = (end - start) / 2; + for (int i = 0; i < numShorts; i++) { + sum += intAbs(shortBuf.get(i)); + } + start += numShorts * 2; + + // see if a singleton byte remains + if (end != start) { + short b = buf.get(start); + + // make it unsigned + if (b < 0) { + b += 256; + } + + sum += b * 256; + } + + sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); + sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF); + int negated = ~sum; + return intAbs((short) negated); + } + + private static int pseudoChecksumIPv4( + ByteBuffer buf, int headerOffset, int protocol, int transportLen) { + int partial = protocol + transportLen; + partial += intAbs(buf.getShort(headerOffset + 12)); + partial += intAbs(buf.getShort(headerOffset + 14)); + partial += intAbs(buf.getShort(headerOffset + 16)); + partial += intAbs(buf.getShort(headerOffset + 18)); + return partial; + } + + private static int pseudoChecksumIPv6( + ByteBuffer buf, int headerOffset, int protocol, int transportLen) { + int partial = protocol + transportLen; + for (int offset = 8; offset < 40; offset += 2) { + partial += intAbs(buf.getShort(headerOffset + offset)); + } + return partial; + } + + private static byte ipversion(ByteBuffer buf, int headerOffset) { + return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4); + } + + public static short ipChecksum(ByteBuffer buf, int headerOffset) { + byte ihl = (byte) (buf.get(headerOffset) & 0x0f); + return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4); + } + + private static short transportChecksum(ByteBuffer buf, int protocol, + int ipOffset, int transportOffset, int transportLen) { + if (transportLen < 0) { + throw new IllegalArgumentException("Transport length < 0: " + transportLen); + } + int sum; + byte ver = ipversion(buf, ipOffset); + if (ver == 4) { + sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen); + } else if (ver == 6) { + sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen); + } else { + throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6"); + } + + sum = checksum(buf, sum, transportOffset, transportOffset + transportLen); + if (protocol == IPPROTO_UDP && sum == 0) { + sum = (short) 0xffff; + } + return (short) sum; + } + + public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) { + int transportLen = intAbs(buf.getShort(transportOffset + 4)); + return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen); + } + + public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset, + int transportLen) { + return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen); + } + + public static String addressAndPortToString(InetAddress address, int port) { + return String.format( + (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d", + address.getHostAddress(), port); + } + + public static boolean isValidUdpOrTcpPort(int port) { + return port > 0 && port < 65536; + } +} diff --git a/services/tests/servicestests/src/android/net/IpUtilsTest.java b/services/tests/servicestests/src/android/net/IpUtilsTest.java new file mode 100644 index 0000000..c2d1608 --- /dev/null +++ b/services/tests/servicestests/src/android/net/IpUtilsTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import android.net.util.IpUtils; +import android.test.suitebuilder.annotation.SmallTest; + +import java.nio.ByteBuffer; + +import junit.framework.TestCase; + + +public class IpUtilsTest extends TestCase { + + private static final int IPV4_HEADER_LENGTH = 20; + private static final int IPV6_HEADER_LENGTH = 40; + private static final int TCP_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + private static final int IP_CHECKSUM_OFFSET = 10; + private static final int TCP_CHECKSUM_OFFSET = 16; + private static final int UDP_CHECKSUM_OFFSET = 6; + + private int getUnsignedByte(ByteBuffer buf, int offset) { + return buf.get(offset) & 0xff; + } + + private int getChecksum(ByteBuffer buf, int offset) { + return getUnsignedByte(buf, offset) * 256 + getUnsignedByte(buf, offset + 1); + } + + private void assertChecksumEquals(int expected, short actual) { + assertEquals(Integer.toHexString(expected), Integer.toHexString(actual & 0xffff)); + } + + // Generate test packets using Python code like this:: + // + // from scapy import all as scapy + // + // def JavaPacketDefinition(bytes): + // out = " ByteBuffer packet = ByteBuffer.wrap(new byte[] {\n " + // for i in xrange(len(bytes)): + // out += "(byte) 0x%02x" % ord(bytes[i]) + // if i < len(bytes) - 1: + // if i % 4 == 3: + // out += ",\n " + // else: + // out += ", " + // out += "\n });" + // return out + // + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2") / + // scapy.UDP(sport=12345, dport=7) / + // "hello") + // print JavaPacketDefinition(str(packet)) + + @SmallTest + public void testIpv6TcpChecksum() throws Exception { + // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) / + // scapy.TCP(sport=12345, dport=7, + // seq=1692871236, ack=128376451, flags=16, + // window=32768) / + // "hello, world") + ByteBuffer packet = ByteBuffer.wrap(new byte[] { + (byte) 0x68, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x20, (byte) 0x06, (byte) 0x40, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + (byte) 0x30, (byte) 0x39, (byte) 0x00, (byte) 0x07, + (byte) 0x64, (byte) 0xe7, (byte) 0x2a, (byte) 0x44, + (byte) 0x07, (byte) 0xa6, (byte) 0xde, (byte) 0x83, + (byte) 0x50, (byte) 0x10, (byte) 0x80, (byte) 0x00, + (byte) 0xee, (byte) 0x71, (byte) 0x00, (byte) 0x00, + (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, + (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77, + (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64 + }); + + // Check that a valid packet has checksum 0. + int transportLen = packet.limit() - IPV6_HEADER_LENGTH; + assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + + // Check that we can calculate the checksum from scratch. + int sumOffset = IPV6_HEADER_LENGTH + TCP_CHECKSUM_OFFSET; + int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1); + assertEquals(0xee71, sum); + + packet.put(sumOffset, (byte) 0); + packet.put(sumOffset + 1, (byte) 0); + assertChecksumEquals(sum, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + + // Check that writing the checksum back into the packet results in a valid packet. + packet.putShort( + sumOffset, + IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen)); + } + + @SmallTest + public void testIpv4UdpChecksum() { + // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) / + // scapy.UDP(sport=32012, dport=4500) / + // "\xff") + ByteBuffer packet = ByteBuffer.wrap(new byte[] { + (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x1d, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x11, (byte) 0xf6, (byte) 0x8b, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01, + (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02, + (byte) 0x7d, (byte) 0x0c, (byte) 0x11, (byte) 0x94, + (byte) 0x00, (byte) 0x09, (byte) 0xee, (byte) 0x36, + (byte) 0xff + }); + + // Check that a valid packet has IP checksum 0 and UDP checksum 0xffff (0 is not a valid + // UDP checksum, so the udpChecksum rewrites 0 to 0xffff). + assertEquals(0, IpUtils.ipChecksum(packet, 0)); + assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + + // Check that we can calculate the checksums from scratch. + final int ipSumOffset = IP_CHECKSUM_OFFSET; + final int ipSum = getChecksum(packet, ipSumOffset); + assertEquals(0xf68b, ipSum); + + packet.put(ipSumOffset, (byte) 0); + packet.put(ipSumOffset + 1, (byte) 0); + assertChecksumEquals(ipSum, IpUtils.ipChecksum(packet, 0)); + + final int udpSumOffset = IPV4_HEADER_LENGTH + UDP_CHECKSUM_OFFSET; + final int udpSum = getChecksum(packet, udpSumOffset); + assertEquals(0xee36, udpSum); + + packet.put(udpSumOffset, (byte) 0); + packet.put(udpSumOffset + 1, (byte) 0); + assertChecksumEquals(udpSum, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + + // Check that writing the checksums back into the packet results in a valid packet. + packet.putShort(ipSumOffset, IpUtils.ipChecksum(packet, 0)); + packet.putShort(udpSumOffset, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + assertEquals(0, IpUtils.ipChecksum(packet, 0)); + assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH)); + } +} |