am 6163290e: am e647d93d: am 8bf977d5: ConnectivityManager API for for packet keepalives.

* commit '6163290e5975b0d4ebdecc20dfa41faac4b4ba79':
  ConnectivityManager API for for packet keepalives.
This commit is contained in:
Lorenzo Colitti
2015-07-18 17:51:42 +00:00
committed by Android Git Automerger
9 changed files with 1104 additions and 7 deletions

View File

@ -1206,6 +1206,142 @@ public class ConnectivityManager {
return true;
}
/** @hide */
public static class PacketKeepaliveCallback {
/** The requested keepalive was successfully started. */
public void onStarted() {}
/** The keepalive was successfully stopped. */
public void onStopped() {}
/** An error occurred. */
public void onError(int error) {}
}
/**
* Allows applications to request that the system periodically send specific packets on their
* behalf, using hardware offload to save battery power.
*
* To request that the system send keepalives, call one of the methods that return a
* {@link ConnectivityManager.PacketKeepalive} object, such as {@link #startNattKeepalive},
* passing in a non-null callback. If the callback is successfully started, the callback's
* {@code onStarted} method will be called. If an error occurs, {@code onError} will be called,
* specifying one of the {@code ERROR_*} constants in this class.
*
* To stop an existing keepalive, call {@link stop}. The system will call {@code onStopped} if
* the operation was successfull or {@code onError} if an error occurred.
*
* @hide
*/
public class PacketKeepalive {
private static final String TAG = "PacketKeepalive";
/** @hide */
public static final int SUCCESS = 0;
/** @hide */
public static final int NO_KEEPALIVE = -1;
/** @hide */
public static final int BINDER_DIED = -10;
/** The specified {@code Network} is not connected. */
public static final int ERROR_INVALID_NETWORK = -20;
/** The specified IP addresses are invalid. For example, the specified source IP address is
* not configured on the specified {@code Network}. */
public static final int ERROR_INVALID_IP_ADDRESS = -21;
/** The requested port is invalid. */
public static final int ERROR_INVALID_PORT = -22;
/** The packet length is invalid (e.g., too long). */
public static final int ERROR_INVALID_LENGTH = -23;
/** The packet transmission interval is invalid (e.g., too short). */
public static final int ERROR_INVALID_INTERVAL = -24;
/** The hardware does not support this request. */
public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
public static final int NATT_PORT = 4500;
private final Network mNetwork;
private final PacketKeepaliveCallback mCallback;
private final Looper mLooper;
private final Messenger mMessenger;
private volatile Integer mSlot;
void stopLooper() {
mLooper.quit();
}
public void stop() {
try {
mService.stopKeepalive(mNetwork, mSlot);
} catch (RemoteException e) {
Log.e(TAG, "Error stopping packet keepalive: ", e);
stopLooper();
}
}
private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
checkNotNull(network, "network cannot be null");
checkNotNull(callback, "callback cannot be null");
mNetwork = network;
mCallback = callback;
HandlerThread thread = new HandlerThread(TAG);
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:
int error = message.arg2;
try {
if (error == SUCCESS) {
if (mSlot == null) {
mSlot = message.arg1;
mCallback.onStarted();
} else {
mSlot = null;
stopLooper();
mCallback.onStopped();
}
} else {
stopLooper();
mCallback.onError(error);
}
} catch (Exception e) {
Log.e(TAG, "Exception in keepalive callback(" + error + ")", e);
}
break;
default:
Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
break;
}
}
});
}
}
/**
* Starts an IPsec NAT-T keepalive packet with the specified parameters.
*
* @hide
*/
public PacketKeepalive startNattKeepalive(
Network network, int intervalSeconds, PacketKeepaliveCallback callback,
InetAddress srcAddr, int srcPort, InetAddress dstAddr) {
final PacketKeepalive k = new PacketKeepalive(network, callback);
try {
mService.startNattKeepalive(network, intervalSeconds, k.mMessenger, new Binder(),
srcAddr.getHostAddress(), srcPort, dstAddr.getHostAddress());
} catch (RemoteException e) {
Log.e(TAG, "Error starting packet keepalive: ", e);
k.stopLooper();
return null;
}
return k;
}
/**
* 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

View File

@ -160,4 +160,9 @@ interface IConnectivityManager
boolean setUnderlyingNetworksForVpn(in Network[] networks);
void factoryReset();
void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
in IBinder binder, String srcAddr, int srcPort, String dstAddr);
void stopKeepalive(in Network network, int slot);
}

View File

@ -25,6 +25,7 @@ import android.util.Log;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import android.net.ConnectivityManager.PacketKeepalive;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@ -143,11 +144,46 @@ public abstract class NetworkAgent extends Handler {
*/
public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
/** Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
/**
* Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
* the underlying network connection for updated bandwidth information.
*/
public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
/**
* Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent
* periodically on the given interval.
*
* arg1 = the slot number of the keepalive to start
* arg2 = interval in seconds
* obj = KeepalivePacketData object describing the data to be sent
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
*/
public static final int CMD_START_PACKET_KEEPALIVE = BASE + 11;
/**
* Requests that the specified keepalive packet be stopped.
*
* arg1 = slot number of the keepalive to stop.
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
*/
public static final int CMD_STOP_PACKET_KEEPALIVE = BASE + 12;
/**
* Sent by the NetworkAgent to ConnectivityService to provide status on a packet keepalive
* request. This may either be the reply to a CMD_START_PACKET_KEEPALIVE, or an asynchronous
* error notification.
*
* This is also sent by KeepaliveTracker to the app's ConnectivityManager.PacketKeepalive to
* so that the app's PacketKeepaliveCallback methods can be called.
*
* arg1 = slot number of the keepalive
* arg2 = error code
*/
public static final int EVENT_PACKET_KEEPALIVE = BASE + 13;
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
this(looper, context, logTag, ni, nc, lp, score, null);
@ -240,18 +276,41 @@ public abstract class NetworkAgent extends Handler {
}
case CMD_SAVE_ACCEPT_UNVALIDATED: {
saveAcceptUnvalidated(msg.arg1 != 0);
break;
}
case CMD_START_PACKET_KEEPALIVE: {
startPacketKeepalive(msg);
break;
}
case CMD_STOP_PACKET_KEEPALIVE: {
stopPacketKeepalive(msg);
break;
}
}
}
private void queueOrSendMessage(int what, Object obj) {
queueOrSendMessage(what, 0, 0, obj);
}
private void queueOrSendMessage(int what, int arg1, int arg2) {
queueOrSendMessage(what, arg1, arg2, null);
}
private void queueOrSendMessage(int what, int arg1, int arg2, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
msg.obj = obj;
queueOrSendMessage(msg);
}
private void queueOrSendMessage(Message msg) {
synchronized (mPreConnectedQueue) {
if (mAsyncChannel != null) {
mAsyncChannel.sendMessage(what, obj);
mAsyncChannel.sendMessage(msg);
} else {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
mPreConnectedQueue.add(msg);
}
}
@ -365,6 +424,27 @@ public abstract class NetworkAgent extends Handler {
protected void saveAcceptUnvalidated(boolean accept) {
}
/**
* Requests that the network hardware send the specified packet at the specified interval.
*/
protected void startPacketKeepalive(Message msg) {
onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
}
/**
* Requests that the network hardware send the specified packet at the specified interval.
*/
protected void stopPacketKeepalive(Message msg) {
onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
}
/**
* Called by the network when a packet keepalive event occurs.
*/
public void onPacketKeepaliveEvent(int slot, int reason) {
queueOrSendMessage(EVENT_PACKET_KEEPALIVE, slot, reason);
}
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}

View File

@ -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)

View File

@ -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;
@ -399,6 +401,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 +768,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 +1455,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 +1850,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:");
@ -2010,6 +2023,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")) {
@ -2148,6 +2170,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
notifyIfacesChanged();
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);
@ -2509,6 +2533,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;
@ -3863,6 +3900,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) {
@ -4662,6 +4701,22 @@ public class ConnectivityService extends IConnectivityManager.Stub
return success;
}
@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();

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}