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
17package com.android.server.connectivity;
18
19import com.android.internal.util.HexDump;
20import com.android.internal.util.IndentingPrintWriter;
21import com.android.server.connectivity.KeepalivePacketData;
22import com.android.server.connectivity.NetworkAgentInfo;
23import android.net.ConnectivityManager;
24import android.net.ConnectivityManager.PacketKeepalive;
25import android.net.LinkAddress;
26import android.net.NetworkAgent;
27import android.net.NetworkUtils;
28import android.net.util.IpUtils;
29import android.os.Binder;
30import android.os.IBinder;
31import android.os.Handler;
32import android.os.Message;
33import android.os.Messenger;
34import android.os.Process;
35import android.os.RemoteException;
36import android.system.OsConstants;
37import android.util.Log;
38import android.util.Pair;
39
40import java.nio.ByteBuffer;
41import java.nio.ByteOrder;
42import java.net.Inet4Address;
43import java.net.Inet6Address;
44import java.net.InetAddress;
45import java.util.ArrayList;
46import java.util.HashMap;
47
48import static android.net.ConnectivityManager.PacketKeepalive.*;
49import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
50import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
51import 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 */
60public 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
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)
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. */
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() {
175            return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL;
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            }
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
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
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
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) {
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
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) {
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
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