/* * 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 > 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(); 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 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 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 networkKeepalives = mKeepalives.get(nai); if (networkKeepalives != null) { ArrayList> 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 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(); } }