Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.connectivity; |
| 18 | |
| 19 | import com.android.internal.util.HexDump; |
| 20 | import com.android.internal.util.IndentingPrintWriter; |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 21 | import com.android.server.connectivity.NetworkAgentInfo; |
| 22 | import android.net.ConnectivityManager; |
| 23 | import android.net.ConnectivityManager.PacketKeepalive; |
Nathan Harold | 26de1d3 | 2017-11-02 21:01:46 -0700 | [diff] [blame] | 24 | import android.net.KeepalivePacketData; |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 25 | import android.net.LinkAddress; |
| 26 | import android.net.NetworkAgent; |
| 27 | import android.net.NetworkUtils; |
| 28 | import android.net.util.IpUtils; |
| 29 | import android.os.Binder; |
| 30 | import android.os.IBinder; |
| 31 | import android.os.Handler; |
| 32 | import android.os.Message; |
| 33 | import android.os.Messenger; |
| 34 | import android.os.Process; |
| 35 | import android.os.RemoteException; |
| 36 | import android.system.OsConstants; |
| 37 | import android.util.Log; |
| 38 | import android.util.Pair; |
| 39 | |
| 40 | import java.nio.ByteBuffer; |
| 41 | import java.nio.ByteOrder; |
| 42 | import java.net.Inet4Address; |
| 43 | import java.net.Inet6Address; |
| 44 | import java.net.InetAddress; |
| 45 | import java.util.ArrayList; |
| 46 | import java.util.HashMap; |
| 47 | |
| 48 | import static android.net.ConnectivityManager.PacketKeepalive.*; |
| 49 | import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE; |
| 50 | import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE; |
| 51 | import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE; |
| 52 | |
| 53 | /** |
| 54 | * Manages packet keepalive requests. |
| 55 | * |
| 56 | * Provides methods to stop and start keepalive requests, and keeps track of keepalives across all |
| 57 | * networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its |
| 58 | * methods must be called only from the ConnectivityService handler thread. |
| 59 | */ |
| 60 | public class KeepaliveTracker { |
| 61 | |
| 62 | private static final String TAG = "KeepaliveTracker"; |
Joe Onorato | 12acbd7 | 2016-02-01 17:49:31 -0800 | [diff] [blame] | 63 | private static final boolean DBG = false; |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 64 | |
Lorenzo Colitti | b988466 | 2015-09-08 22:02:37 +0900 | [diff] [blame] | 65 | public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD; |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 66 | |
| 67 | /** Keeps track of keepalive requests. */ |
| 68 | private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives = |
| 69 | new HashMap<> (); |
| 70 | private final Handler mConnectivityServiceHandler; |
| 71 | |
| 72 | public KeepaliveTracker(Handler handler) { |
| 73 | mConnectivityServiceHandler = handler; |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Tracks information about a packet keepalive. |
| 78 | * |
| 79 | * All information about this keepalive is known at construction time except the slot number, |
| 80 | * which is only returned when the hardware has successfully started the keepalive. |
| 81 | */ |
| 82 | class KeepaliveInfo implements IBinder.DeathRecipient { |
| 83 | // Bookkeping data. |
| 84 | private final Messenger mMessenger; |
| 85 | private final IBinder mBinder; |
| 86 | private final int mUid; |
| 87 | private final int mPid; |
| 88 | private final NetworkAgentInfo mNai; |
| 89 | |
| 90 | /** Keepalive slot. A small integer that identifies this keepalive among the ones handled |
| 91 | * by this network. */ |
| 92 | private int mSlot = PacketKeepalive.NO_KEEPALIVE; |
| 93 | |
| 94 | // Packet data. |
| 95 | private final KeepalivePacketData mPacket; |
| 96 | private final int mInterval; |
| 97 | |
| 98 | // Whether the keepalive is started or not. |
| 99 | public boolean isStarted; |
| 100 | |
| 101 | public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai, |
| 102 | KeepalivePacketData packet, int interval) { |
| 103 | mMessenger = messenger; |
| 104 | mBinder = binder; |
| 105 | mPid = Binder.getCallingPid(); |
| 106 | mUid = Binder.getCallingUid(); |
| 107 | |
| 108 | mNai = nai; |
| 109 | mPacket = packet; |
| 110 | mInterval = interval; |
| 111 | |
| 112 | try { |
| 113 | mBinder.linkToDeath(this, 0); |
| 114 | } catch (RemoteException e) { |
| 115 | binderDied(); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | public NetworkAgentInfo getNai() { |
| 120 | return mNai; |
| 121 | } |
| 122 | |
| 123 | public String toString() { |
| 124 | return new StringBuffer("KeepaliveInfo [") |
| 125 | .append(" network=").append(mNai.network) |
| 126 | .append(" isStarted=").append(isStarted) |
| 127 | .append(" ") |
| 128 | .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort)) |
| 129 | .append("->") |
| 130 | .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)) |
| 131 | .append(" interval=" + mInterval) |
Nathan Harold | 70224716 | 2017-12-06 19:07:32 -0800 | [diff] [blame^] | 132 | .append(" packetData=" + HexDump.toHexString(mPacket.getPacket())) |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 133 | .append(" uid=").append(mUid).append(" pid=").append(mPid) |
| 134 | .append(" ]") |
| 135 | .toString(); |
| 136 | } |
| 137 | |
| 138 | /** Sends a message back to the application via its PacketKeepalive.Callback. */ |
| 139 | void notifyMessenger(int slot, int err) { |
| 140 | KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err); |
| 141 | } |
| 142 | |
| 143 | /** Called when the application process is killed. */ |
| 144 | public void binderDied() { |
| 145 | // Not called from ConnectivityService handler thread, so send it a message. |
| 146 | mConnectivityServiceHandler.obtainMessage( |
| 147 | NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, |
| 148 | mSlot, PacketKeepalive.BINDER_DIED, mNai.network).sendToTarget(); |
| 149 | } |
| 150 | |
| 151 | void unlinkDeathRecipient() { |
| 152 | if (mBinder != null) { |
| 153 | mBinder.unlinkToDeath(this, 0); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | private int checkNetworkConnected() { |
| 158 | if (!mNai.networkInfo.isConnectedOrConnecting()) { |
| 159 | return ERROR_INVALID_NETWORK; |
| 160 | } |
| 161 | return SUCCESS; |
| 162 | } |
| 163 | |
| 164 | private int checkSourceAddress() { |
| 165 | // Check that we have the source address. |
| 166 | for (InetAddress address : mNai.linkProperties.getAddresses()) { |
| 167 | if (address.equals(mPacket.srcAddress)) { |
| 168 | return SUCCESS; |
| 169 | } |
| 170 | } |
| 171 | return ERROR_INVALID_IP_ADDRESS; |
| 172 | } |
| 173 | |
| 174 | private int checkInterval() { |
Nathan Harold | 70224716 | 2017-12-06 19:07:32 -0800 | [diff] [blame^] | 175 | return mInterval >= 10 ? SUCCESS : ERROR_INVALID_INTERVAL; |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | private int isValid() { |
| 179 | synchronized (mNai) { |
| 180 | int error = checkInterval(); |
| 181 | if (error == SUCCESS) error = checkNetworkConnected(); |
| 182 | if (error == SUCCESS) error = checkSourceAddress(); |
| 183 | return error; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | void start(int slot) { |
| 188 | int error = isValid(); |
| 189 | if (error == SUCCESS) { |
| 190 | mSlot = slot; |
| 191 | Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name()); |
| 192 | mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket); |
| 193 | } else { |
| 194 | notifyMessenger(NO_KEEPALIVE, error); |
| 195 | return; |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | void stop(int reason) { |
| 200 | int uid = Binder.getCallingUid(); |
| 201 | if (uid != mUid && uid != Process.SYSTEM_UID) { |
| 202 | if (DBG) { |
| 203 | Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network); |
| 204 | } |
| 205 | } |
| 206 | if (isStarted) { |
| 207 | Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name()); |
| 208 | mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot); |
| 209 | } |
Lorenzo Colitti | 9acca09 | 2015-09-08 13:44:23 +0900 | [diff] [blame] | 210 | // TODO: at the moment we unconditionally return failure here. In cases where the |
| 211 | // NetworkAgent is alive, should we ask it to reply, so it can return failure? |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 212 | notifyMessenger(mSlot, reason); |
| 213 | unlinkDeathRecipient(); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | void notifyMessenger(Messenger messenger, int slot, int err) { |
| 218 | Message message = Message.obtain(); |
| 219 | message.what = EVENT_PACKET_KEEPALIVE; |
| 220 | message.arg1 = slot; |
| 221 | message.arg2 = err; |
| 222 | message.obj = null; |
| 223 | try { |
| 224 | messenger.send(message); |
| 225 | } catch (RemoteException e) { |
| 226 | // Process died? |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | private int findFirstFreeSlot(NetworkAgentInfo nai) { |
| 231 | HashMap networkKeepalives = mKeepalives.get(nai); |
| 232 | if (networkKeepalives == null) { |
| 233 | networkKeepalives = new HashMap<Integer, KeepaliveInfo>(); |
| 234 | mKeepalives.put(nai, networkKeepalives); |
| 235 | } |
| 236 | |
Lorenzo Colitti | 9acca09 | 2015-09-08 13:44:23 +0900 | [diff] [blame] | 237 | // Find the lowest-numbered free slot. Slot numbers start from 1, because that's what two |
| 238 | // separate chipset implementations independently came up with. |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 239 | int slot; |
Lorenzo Colitti | 9acca09 | 2015-09-08 13:44:23 +0900 | [diff] [blame] | 240 | for (slot = 1; slot <= networkKeepalives.size(); slot++) { |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 241 | if (networkKeepalives.get(slot) == null) { |
| 242 | return slot; |
| 243 | } |
| 244 | } |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 245 | return slot; |
| 246 | } |
| 247 | |
| 248 | public void handleStartKeepalive(Message message) { |
| 249 | KeepaliveInfo ki = (KeepaliveInfo) message.obj; |
| 250 | NetworkAgentInfo nai = ki.getNai(); |
| 251 | int slot = findFirstFreeSlot(nai); |
| 252 | mKeepalives.get(nai).put(slot, ki); |
| 253 | ki.start(slot); |
| 254 | } |
| 255 | |
| 256 | public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) { |
| 257 | HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); |
| 258 | if (networkKeepalives != null) { |
| 259 | for (KeepaliveInfo ki : networkKeepalives.values()) { |
| 260 | ki.stop(reason); |
| 261 | } |
| 262 | networkKeepalives.clear(); |
| 263 | mKeepalives.remove(nai); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) { |
Lorenzo Colitti | 9acca09 | 2015-09-08 13:44:23 +0900 | [diff] [blame] | 268 | String networkName = (nai == null) ? "(null)" : nai.name(); |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 269 | HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); |
| 270 | if (networkKeepalives == null) { |
Lorenzo Colitti | 9acca09 | 2015-09-08 13:44:23 +0900 | [diff] [blame] | 271 | Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName); |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 272 | return; |
| 273 | } |
| 274 | KeepaliveInfo ki = networkKeepalives.get(slot); |
| 275 | if (ki == null) { |
Lorenzo Colitti | 9acca09 | 2015-09-08 13:44:23 +0900 | [diff] [blame] | 276 | Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName); |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 277 | return; |
| 278 | } |
| 279 | ki.stop(reason); |
| 280 | networkKeepalives.remove(slot); |
| 281 | if (networkKeepalives.isEmpty()) { |
| 282 | mKeepalives.remove(nai); |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) { |
| 287 | HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); |
| 288 | if (networkKeepalives != null) { |
| 289 | ArrayList<Pair<Integer, Integer>> invalidKeepalives = new ArrayList<>(); |
| 290 | for (int slot : networkKeepalives.keySet()) { |
| 291 | int error = networkKeepalives.get(slot).isValid(); |
| 292 | if (error != SUCCESS) { |
| 293 | invalidKeepalives.add(Pair.create(slot, error)); |
| 294 | } |
| 295 | } |
| 296 | for (Pair<Integer, Integer> slotAndError: invalidKeepalives) { |
| 297 | handleStopKeepalive(nai, slotAndError.first, slotAndError.second); |
| 298 | } |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) { |
| 303 | int slot = message.arg1; |
| 304 | int reason = message.arg2; |
| 305 | |
| 306 | KeepaliveInfo ki = null; |
| 307 | try { |
| 308 | ki = mKeepalives.get(nai).get(slot); |
| 309 | } catch(NullPointerException e) {} |
| 310 | if (ki == null) { |
| 311 | Log.e(TAG, "Event for unknown keepalive " + slot + " on " + nai.name()); |
| 312 | return; |
| 313 | } |
| 314 | |
| 315 | if (reason == SUCCESS && !ki.isStarted) { |
| 316 | // Keepalive successfully started. |
| 317 | if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name()); |
| 318 | ki.isStarted = true; |
| 319 | ki.notifyMessenger(slot, reason); |
| 320 | } else { |
| 321 | // Keepalive successfully stopped, or error. |
| 322 | ki.isStarted = false; |
| 323 | if (reason == SUCCESS) { |
| 324 | if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); |
| 325 | } else { |
| 326 | if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); |
| 327 | } |
| 328 | handleStopKeepalive(nai, slot, reason); |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, |
| 333 | IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { |
Lorenzo Colitti | 9acca09 | 2015-09-08 13:44:23 +0900 | [diff] [blame] | 334 | if (nai == null) { |
| 335 | notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); |
| 336 | return; |
| 337 | } |
| 338 | |
Lorenzo Colitti | 8bf977d | 2015-06-15 14:29:22 +0900 | [diff] [blame] | 339 | InetAddress srcAddress, dstAddress; |
| 340 | try { |
| 341 | srcAddress = NetworkUtils.numericToInetAddress(srcAddrString); |
| 342 | dstAddress = NetworkUtils.numericToInetAddress(dstAddrString); |
| 343 | } catch (IllegalArgumentException e) { |
| 344 | notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_IP_ADDRESS); |
| 345 | return; |
| 346 | } |
| 347 | |
| 348 | KeepalivePacketData packet; |
| 349 | try { |
| 350 | packet = KeepalivePacketData.nattKeepalivePacket( |
| 351 | srcAddress, srcPort, dstAddress, NATT_PORT); |
| 352 | } catch (KeepalivePacketData.InvalidPacketException e) { |
| 353 | notifyMessenger(messenger, NO_KEEPALIVE, e.error); |
| 354 | return; |
| 355 | } |
| 356 | KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds); |
| 357 | Log.d(TAG, "Created keepalive: " + ki.toString()); |
| 358 | mConnectivityServiceHandler.obtainMessage( |
| 359 | NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget(); |
| 360 | } |
| 361 | |
| 362 | public void dump(IndentingPrintWriter pw) { |
| 363 | pw.println("Packet keepalives:"); |
| 364 | pw.increaseIndent(); |
| 365 | for (NetworkAgentInfo nai : mKeepalives.keySet()) { |
| 366 | pw.println(nai.name()); |
| 367 | pw.increaseIndent(); |
| 368 | for (int slot : mKeepalives.get(nai).keySet()) { |
| 369 | KeepaliveInfo ki = mKeepalives.get(nai).get(slot); |
| 370 | pw.println(slot + ": " + ki.toString()); |
| 371 | } |
| 372 | pw.decreaseIndent(); |
| 373 | } |
| 374 | pw.decreaseIndent(); |
| 375 | } |
| 376 | } |