ConnectivityManager API for for packet keepalives.
Bug: 21405946 Change-Id: Ie1f8f8bee684fe2bb1092a9f1bc9f5dc29b1defc
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
|
||||
|
||||
@@ -162,4 +162,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -409,6 +411,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;
|
||||
@@ -750,6 +754,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) {
|
||||
@@ -1435,6 +1441,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);
|
||||
@@ -1826,10 +1836,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:");
|
||||
@@ -1991,6 +2004,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")) {
|
||||
@@ -2127,6 +2149,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);
|
||||
@@ -2565,6 +2589,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;
|
||||
@@ -3928,6 +3965,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) {
|
||||
@@ -4725,6 +4764,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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user