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;
21 import com.android.server.connectivity.KeepalivePacketData;
22 import com.android.server.connectivity.NetworkAgentInfo;
23 import android.net.ConnectivityManager;
24 import android.net.ConnectivityManager.PacketKeepalive;
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";
63     private static final boolean DBG = false;
64 
65     public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
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 
KeepaliveTracker(Handler handler)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 
KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai, KeepalivePacketData packet, int interval)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 
getNai()119         public NetworkAgentInfo getNai() {
120             return mNai;
121         }
122 
toString()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)
132                     .append(" data=" + HexDump.toHexString(mPacket.data))
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. */
notifyMessenger(int slot, int err)139         void notifyMessenger(int slot, int err) {
140             KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err);
141         }
142 
143         /** Called when the application process is killed. */
binderDied()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 
unlinkDeathRecipient()151         void unlinkDeathRecipient() {
152             if (mBinder != null) {
153                 mBinder.unlinkToDeath(this, 0);
154             }
155         }
156 
checkNetworkConnected()157         private int checkNetworkConnected() {
158             if (!mNai.networkInfo.isConnectedOrConnecting()) {
159                 return ERROR_INVALID_NETWORK;
160             }
161             return SUCCESS;
162         }
163 
checkSourceAddress()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 
checkInterval()174         private int checkInterval() {
175             return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL;
176         }
177 
isValid()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 
start(int slot)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 
stop(int reason)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             }
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?
212             notifyMessenger(mSlot, reason);
213             unlinkDeathRecipient();
214         }
215     }
216 
notifyMessenger(Messenger messenger, int slot, int err)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 
findFirstFreeSlot(NetworkAgentInfo nai)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 
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.
239         int slot;
240         for (slot = 1; slot <= networkKeepalives.size(); slot++) {
241             if (networkKeepalives.get(slot) == null) {
242                 return slot;
243             }
244         }
245         return slot;
246     }
247 
handleStartKeepalive(Message message)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 
handleStopAllKeepalives(NetworkAgentInfo nai, int reason)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 
handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason)267     public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
268         String networkName = (nai == null) ? "(null)" : nai.name();
269         HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
270         if (networkKeepalives == null) {
271             Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName);
272             return;
273         }
274         KeepaliveInfo ki = networkKeepalives.get(slot);
275         if (ki == null) {
276             Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName);
277             return;
278         }
279         ki.stop(reason);
280         networkKeepalives.remove(slot);
281         if (networkKeepalives.isEmpty()) {
282             mKeepalives.remove(nai);
283         }
284     }
285 
handleCheckKeepalivesStillValid(NetworkAgentInfo nai)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 
handleEventPacketKeepalive(NetworkAgentInfo nai, Message message)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 
startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort)332     public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
333             IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
334         if (nai == null) {
335             notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
336             return;
337         }
338 
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 
dump(IndentingPrintWriter pw)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 }
377