* commit '6163290e5975b0d4ebdecc20dfa41faac4b4ba79': ConnectivityManager API for for packet keepalives.
This commit is contained in:
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
151
services/net/java/android/net/util/IpUtils.java
Normal file
151
services/net/java/android/net/util/IpUtils.java
Normal 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;
|
||||
}
|
||||
}
|
162
services/tests/servicestests/src/android/net/IpUtilsTest.java
Normal file
162
services/tests/servicestests/src/android/net/IpUtilsTest.java
Normal 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));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user