1/*
2 * Copyright (C) 2016 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.net;
18
19import static android.net.NetworkPolicyManager.POLICY_NONE;
20import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
21import static android.net.wifi.WifiInfo.removeDoubleQuotes;
22import static com.android.server.net.NetworkPolicyManagerService.newWifiPolicy;
23import static com.android.server.net.NetworkPolicyManagerService.TAG;
24
25import java.io.PrintWriter;
26import java.util.ArrayList;
27import java.util.HashSet;
28import java.util.List;
29import java.util.Set;
30
31import android.content.Context;
32import android.net.INetworkPolicyManager;
33import android.net.NetworkPolicy;
34import android.net.NetworkTemplate;
35import android.net.wifi.WifiConfiguration;
36import android.net.wifi.WifiManager;
37import android.os.RemoteException;
38import android.os.ShellCommand;
39import android.util.Log;
40
41class NetworkPolicyManagerShellCommand extends ShellCommand {
42
43    private final INetworkPolicyManager mInterface;
44    private final WifiManager mWifiManager;
45
46    NetworkPolicyManagerShellCommand(Context context, INetworkPolicyManager service) {
47        mInterface = service;
48        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
49    }
50
51    @Override
52    public int onCommand(String cmd) {
53        if (cmd == null) {
54            return handleDefaultCommands(cmd);
55        }
56        final PrintWriter pw = getOutPrintWriter();
57        try {
58            switch(cmd) {
59                case "get":
60                    return runGet();
61                case "set":
62                    return runSet();
63                case "list":
64                    return runList();
65                case "add":
66                    return runAdd();
67                case "remove":
68                    return runRemove();
69                default:
70                    return handleDefaultCommands(cmd);
71            }
72        } catch (RemoteException e) {
73            pw.println("Remote exception: " + e);
74        }
75        return -1;
76    }
77
78    @Override
79    public void onHelp() {
80        final PrintWriter pw = getOutPrintWriter();
81        pw.println("Network policy manager (netpolicy) commands:");
82        pw.println("  help");
83        pw.println("    Print this help text.");
84        pw.println("");
85        pw.println("  add restrict-background-whitelist UID");
86        pw.println("    Adds a UID to the whitelist for restrict background usage.");
87        pw.println("  add restrict-background-blacklist UID");
88        pw.println("    Adds a UID to the blacklist for restrict background usage.");
89        pw.println("  get restrict-background");
90        pw.println("    Gets the global restrict background usage status.");
91        pw.println("  list wifi-networks [BOOLEAN]");
92        pw.println("    Lists all saved wifi networks and whether they are metered or not.");
93        pw.println("    If a boolean argument is passed, filters just the metered (or unmetered)");
94        pw.println("    networks.");
95        pw.println("  list restrict-background-whitelist");
96        pw.println("    Lists UIDs that are whitelisted for restrict background usage.");
97        pw.println("  list restrict-background-blacklist");
98        pw.println("    Lists UIDs that are blacklisted for restrict background usage.");
99        pw.println("  remove restrict-background-whitelist UID");
100        pw.println("    Removes a UID from the whitelist for restrict background usage.");
101        pw.println("  remove restrict-background-blacklist UID");
102        pw.println("    Removes a UID from the blacklist for restrict background usage.");
103        pw.println("  set metered-network ID BOOLEAN");
104        pw.println("    Toggles whether the given wi-fi network is metered.");
105        pw.println("  set restrict-background BOOLEAN");
106        pw.println("    Sets the global restrict background usage status.");
107    }
108
109    private int runGet() throws RemoteException {
110        final PrintWriter pw = getOutPrintWriter();
111        final String type = getNextArg();
112        if (type == null) {
113            pw.println("Error: didn't specify type of data to get");
114            return -1;
115        }
116        switch(type) {
117            case "restrict-background":
118                return getRestrictBackground();
119        }
120        pw.println("Error: unknown get type '" + type + "'");
121        return -1;
122    }
123
124    private int runSet() throws RemoteException  {
125        final PrintWriter pw = getOutPrintWriter();
126        final String type = getNextArg();
127        if (type == null) {
128            pw.println("Error: didn't specify type of data to set");
129            return -1;
130        }
131        switch(type) {
132            case "metered-network":
133                return setMeteredWifiNetwork();
134            case "restrict-background":
135                return setRestrictBackground();
136        }
137        pw.println("Error: unknown set type '" + type + "'");
138        return -1;
139    }
140
141    private int runList() throws RemoteException  {
142        final PrintWriter pw = getOutPrintWriter();
143        final String type = getNextArg();
144        if (type == null) {
145            pw.println("Error: didn't specify type of data to list");
146            return -1;
147        }
148        switch(type) {
149            case "wifi-networks":
150                return listWifiNetworks();
151            case "restrict-background-whitelist":
152                return listRestrictBackgroundWhitelist();
153            case "restrict-background-blacklist":
154                return listRestrictBackgroundBlacklist();
155        }
156        pw.println("Error: unknown list type '" + type + "'");
157        return -1;
158    }
159
160    private int runAdd() throws RemoteException  {
161        final PrintWriter pw = getOutPrintWriter();
162        final String type = getNextArg();
163        if (type == null) {
164            pw.println("Error: didn't specify type of data to add");
165            return -1;
166        }
167        switch(type) {
168            case "restrict-background-whitelist":
169                return addRestrictBackgroundWhitelist();
170            case "restrict-background-blacklist":
171                return addRestrictBackgroundBlacklist();
172        }
173        pw.println("Error: unknown add type '" + type + "'");
174        return -1;
175    }
176
177    private int runRemove() throws RemoteException {
178        final PrintWriter pw = getOutPrintWriter();
179        final String type = getNextArg();
180        if (type == null) {
181            pw.println("Error: didn't specify type of data to remove");
182            return -1;
183        }
184        switch(type) {
185            case "restrict-background-whitelist":
186                return removeRestrictBackgroundWhitelist();
187            case "restrict-background-blacklist":
188                return removeRestrictBackgroundBlacklist();
189        }
190        pw.println("Error: unknown remove type '" + type + "'");
191        return -1;
192    }
193
194    private int listRestrictBackgroundWhitelist() throws RemoteException {
195        final PrintWriter pw = getOutPrintWriter();
196        final int[] uids = mInterface.getRestrictBackgroundWhitelistedUids();
197        pw.print("Restrict background whitelisted UIDs: ");
198        if (uids.length == 0) {
199            pw.println("none");
200        } else {
201            for (int i = 0; i < uids.length; i++) {
202                int uid = uids[i];
203                pw.print(uid);
204                pw.print(' ');
205            }
206        }
207        pw.println();
208        return 0;
209    }
210
211    private int listRestrictBackgroundBlacklist() throws RemoteException {
212        final PrintWriter pw = getOutPrintWriter();
213
214        final int[] uids = mInterface.getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND);
215        pw.print("Restrict background blacklisted UIDs: ");
216        if (uids.length == 0) {
217            pw.println("none");
218        } else {
219            for (int i = 0; i < uids.length; i++) {
220                int uid = uids[i];
221                pw.print(uid);
222                pw.print(' ');
223            }
224        }
225        pw.println();
226        return 0;
227    }
228
229    private int getRestrictBackground() throws RemoteException {
230        final PrintWriter pw = getOutPrintWriter();
231        pw.print("Restrict background status: ");
232        pw.println(mInterface.getRestrictBackground() ? "enabled" : "disabled");
233        return 0;
234    }
235
236    private int setRestrictBackground() throws RemoteException {
237        final int enabled = getNextBooleanArg();
238        if (enabled < 0) {
239            return enabled;
240        }
241        mInterface.setRestrictBackground(enabled > 0);
242        return 0;
243    }
244
245    private int addRestrictBackgroundWhitelist() throws RemoteException {
246      final int uid = getUidFromNextArg();
247      if (uid < 0) {
248          return uid;
249      }
250      mInterface.addRestrictBackgroundWhitelistedUid(uid);
251      return 0;
252    }
253
254    private int removeRestrictBackgroundWhitelist() throws RemoteException {
255        final int uid = getUidFromNextArg();
256        if (uid < 0) {
257            return uid;
258        }
259        mInterface.removeRestrictBackgroundWhitelistedUid(uid);
260        return 0;
261    }
262
263    private int addRestrictBackgroundBlacklist() throws RemoteException {
264        final int uid = getUidFromNextArg();
265        if (uid < 0) {
266            return uid;
267        }
268        mInterface.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
269        return 0;
270    }
271
272    private int removeRestrictBackgroundBlacklist() throws RemoteException {
273        final int uid = getUidFromNextArg();
274        if (uid < 0) {
275            return uid;
276        }
277        mInterface.setUidPolicy(uid, POLICY_NONE);
278        return 0;
279    }
280
281    private int listWifiNetworks() throws RemoteException {
282        final PrintWriter pw = getOutPrintWriter();
283        final String arg = getNextArg();
284        final Boolean filter = arg == null ? null : Boolean.valueOf(arg);
285        for (NetworkPolicy policy : getWifiPolicies()) {
286            if (filter != null && filter.booleanValue() != policy.metered) {
287                continue;
288            }
289            pw.print(getNetworkId(policy));
290            pw.print(';');
291            pw.println(policy.metered);
292        }
293        return 0;
294    }
295
296    private int setMeteredWifiNetwork() throws RemoteException {
297        final PrintWriter pw = getOutPrintWriter();
298        final String id = getNextArg();
299        if (id == null) {
300            pw.println("Error: didn't specify ID");
301            return -1;
302        }
303        final String arg = getNextArg();
304        if (arg == null) {
305            pw.println("Error: didn't specify BOOLEAN");
306            return -1;
307        }
308        final boolean metered = Boolean.valueOf(arg);
309        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
310        boolean changed = false;
311        // First try to find a policy with such id
312        for (NetworkPolicy policy : policies) {
313            if (policy.template.isMatchRuleMobile() || policy.metered == metered) {
314                continue;
315            }
316            final String networkId = getNetworkId(policy);
317            if (id.equals(networkId)) {
318                Log.i(TAG, "Changing " + networkId + " metered status to " + metered);
319                policy.metered = metered;
320                changed = true;
321            }
322        }
323        if (changed) {
324            mInterface.setNetworkPolicies(policies);
325            return 0;
326        }
327        // Policy not found: check if there is a saved wi-fi with such id.
328        for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) {
329            final String ssid = removeDoubleQuotes(config.SSID);
330            if (id.equals(ssid)) {
331                final NetworkPolicy policy = newPolicy(ssid);
332                policy.metered = true;
333                Log.i(TAG, "Creating new policy for " + ssid + ": " + policy);
334                final NetworkPolicy[] newPolicies = new NetworkPolicy[policies.length + 1];
335                System.arraycopy(policies, 0, newPolicies, 0, policies.length);
336                newPolicies[newPolicies.length - 1] = policy;
337                mInterface.setNetworkPolicies(newPolicies);
338            }
339        }
340        return 0;
341    }
342
343    private List<NetworkPolicy> getWifiPolicies() throws RemoteException {
344        // First gets a list of saved wi-fi networks.
345        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
346        final int size = configs != null ? configs.size() : 0;
347        final Set<String> ssids = new HashSet<>(size);
348        if (configs != null) {
349            for (WifiConfiguration config : configs) {
350                ssids.add(removeDoubleQuotes(config.SSID));
351            }
352        }
353
354        // Then gets the saved policies.
355        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
356        final List<NetworkPolicy> wifiPolicies = new ArrayList<NetworkPolicy>(policies.length);
357        for (NetworkPolicy policy: policies) {
358            if (!policy.template.isMatchRuleMobile()) {
359                wifiPolicies.add(policy);
360                final String netId = getNetworkId(policy);
361                ssids.remove(netId);
362            }
363        }
364        // Finally, creates new default policies for saved WI-FIs not policied yet.
365        for (String ssid : ssids) {
366            final NetworkPolicy policy = newPolicy(ssid);
367            wifiPolicies.add(policy);
368        }
369        return wifiPolicies;
370    }
371
372    private NetworkPolicy newPolicy(String ssid) {
373        final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(ssid);
374        final NetworkPolicy policy = newWifiPolicy(template, false);
375        return policy;
376    }
377
378    private String getNetworkId(NetworkPolicy policy) {
379        return removeDoubleQuotes(policy.template.getNetworkId());
380    }
381
382    private int getNextBooleanArg() {
383        final PrintWriter pw = getOutPrintWriter();
384        final String arg = getNextArg();
385        if (arg == null) {
386            pw.println("Error: didn't specify BOOLEAN");
387            return -1;
388        }
389        return Boolean.valueOf(arg) ? 1 : 0;
390    }
391
392    private int getUidFromNextArg() {
393        final PrintWriter pw = getOutPrintWriter();
394        final String arg = getNextArg();
395        if (arg == null) {
396            pw.println("Error: didn't specify UID");
397            return -1;
398        }
399        try {
400            return Integer.parseInt(arg);
401        } catch (NumberFormatException e) {
402            pw.println("Error: UID (" + arg + ") should be a number");
403            return -2;
404        }
405    }
406}
407