blob: 9e1f6b85ce390903f484419bdde66e938fb58a96 [file] [log] [blame]
/*
* 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 = false;
public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
/** 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);
}
// TODO: at the moment we unconditionally return failure here. In cases where the
// NetworkAgent is alive, should we ask it to reply, so it can return failure?
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. Slot numbers start from 1, because that's what two
// separate chipset implementations independently came up with.
int slot;
for (slot = 1; slot <= networkKeepalives.size(); slot++) {
if (networkKeepalives.get(slot) == null) {
return slot;
}
}
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) {
String networkName = (nai == null) ? "(null)" : nai.name();
HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
if (networkKeepalives == null) {
Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName);
return;
}
KeepaliveInfo ki = networkKeepalives.get(slot);
if (ki == null) {
Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName);
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) {
if (nai == null) {
notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
return;
}
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();
}
}