2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.connectivity;
19 import static android.Manifest.permission.CHANGE_NETWORK_STATE;
20 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
21 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
22 import static android.Manifest.permission.INTERNET;
23 import static android.Manifest.permission.NETWORK_STACK;
24 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
25 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
26 import static android.content.pm.PackageManager.GET_PERMISSIONS;
27 import static android.content.pm.PackageManager.MATCH_ANY_USER;
28 import static android.os.Process.INVALID_UID;
29 import static android.os.Process.SYSTEM_UID;
31 import android.annotation.NonNull;
32 import android.content.Context;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.PackageManager.NameNotFoundException;
37 import android.content.pm.PackageManagerInternal;
38 import android.content.pm.UserInfo;
39 import android.net.INetd;
40 import android.net.UidRange;
41 import android.os.Build;
42 import android.os.RemoteException;
43 import android.os.ServiceSpecificException;
44 import android.os.UserHandle;
45 import android.os.UserManager;
46 import android.system.OsConstants;
47 import android.util.ArraySet;
48 import android.util.Log;
49 import android.util.SparseArray;
50 import android.util.SparseIntArray;
52 import com.android.internal.annotations.GuardedBy;
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.internal.util.ArrayUtils;
55 import com.android.internal.util.IndentingPrintWriter;
56 import com.android.server.LocalServices;
57 import com.android.server.SystemConfig;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.HashMap;
62 import java.util.HashSet;
63 import java.util.List;
65 import java.util.Map.Entry;
70 * A utility class to inform Netd of UID permisisons.
71 * Does a mass update at boot and then monitors for app install/remove.
75 public class PermissionMonitor {
76 private static final String TAG = "PermissionMonitor";
77 private static final boolean DBG = true;
78 protected static final Boolean SYSTEM = Boolean.TRUE;
79 protected static final Boolean NETWORK = Boolean.FALSE;
80 private static final int VERSION_Q = Build.VERSION_CODES.Q;
82 private final PackageManager mPackageManager;
83 private final UserManager mUserManager;
84 private final INetd mNetd;
86 // Values are User IDs.
88 private final Set<Integer> mUsers = new HashSet<>();
90 // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
92 private final Map<Integer, Boolean> mApps = new HashMap<>();
94 // Keys are active non-bypassable and fully-routed VPN's interface name, Values are uid ranges
95 // for apps under the VPN
97 private final Map<String, Set<UidRange>> mVpnUidRanges = new HashMap<>();
99 // A set of appIds for apps across all users on the device. We track appIds instead of uids
100 // directly to reduce its size and also eliminate the need to update this set when user is
103 private final Set<Integer> mAllApps = new HashSet<>();
105 private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
107 private int getPermissionForUid(int uid) {
109 // Check all the packages for this UID. The UID has the permission if any of the
110 // packages in it has the permission.
111 String[] packages = mPackageManager.getPackagesForUid(uid);
112 if (packages != null && packages.length > 0) {
113 for (String name : packages) {
114 final PackageInfo app = getPackageInfo(name);
115 if (app != null && app.requestedPermissions != null) {
116 permission |= getNetdPermissionMask(app.requestedPermissions,
117 app.requestedPermissionsFlags);
121 // The last package of this uid is removed from device. Clean the package up.
122 permission = INetd.PERMISSION_UNINSTALLED;
128 public void onPackageAdded(String packageName, int uid) {
129 sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
133 public void onPackageChanged(@NonNull String packageName, int uid) {
134 sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
138 public void onPackageRemoved(String packageName, int uid) {
139 sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
143 public PermissionMonitor(Context context, INetd netd) {
144 mPackageManager = context.getPackageManager();
145 mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
149 // Intended to be called only once at startup, after the system is ready. Installs a broadcast
150 // receiver to monitor ongoing UID changes, so this shouldn't/needn't be called again.
151 public synchronized void startMonitoring() {
154 PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
156 pmi.getPackageList(new PackageListObserver());
158 loge("failed to get the PackageManagerInternal service");
160 List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
167 SparseIntArray netdPermsUids = new SparseIntArray();
169 for (PackageInfo app : apps) {
170 int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID;
174 mAllApps.add(UserHandle.getAppId(uid));
176 boolean isNetwork = hasNetworkPermission(app);
177 boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
179 if (isNetwork || hasRestrictedPermission) {
180 Boolean permission = mApps.get(uid);
181 // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
182 // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
183 if (permission == null || permission == NETWORK) {
184 mApps.put(uid, hasRestrictedPermission);
188 //TODO: unify the management of the permissions into one codepath.
189 int otherNetdPerms = getNetdPermissionMask(app.requestedPermissions,
190 app.requestedPermissionsFlags);
191 netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
194 List<UserInfo> users = mUserManager.getUsers(true); // exclude dying users
196 for (UserInfo user : users) {
201 final SparseArray<ArraySet<String>> systemPermission =
202 SystemConfig.getInstance().getSystemPermissions();
203 for (int i = 0; i < systemPermission.size(); i++) {
204 ArraySet<String> perms = systemPermission.valueAt(i);
205 int uid = systemPermission.keyAt(i);
206 int netdPermission = 0;
207 // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission.
209 netdPermission |= perms.contains(UPDATE_DEVICE_STATS)
210 ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0;
211 netdPermission |= perms.contains(INTERNET)
212 ? INetd.PERMISSION_INTERNET : 0;
214 netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
216 log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
217 update(mUsers, mApps, true);
218 sendPackagePermissionsToNetd(netdPermsUids);
222 static boolean isVendorApp(@NonNull ApplicationInfo appInfo) {
223 return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct();
227 protected int getDeviceFirstSdkInt() {
228 return Build.VERSION.FIRST_SDK_INT;
232 boolean hasPermission(PackageInfo app, String permission) {
233 if (app.requestedPermissions != null) {
234 for (String p : app.requestedPermissions) {
235 if (permission.equals(p)) {
243 private boolean hasNetworkPermission(PackageInfo app) {
244 return hasPermission(app, CHANGE_NETWORK_STATE);
247 private boolean hasRestrictedNetworkPermission(PackageInfo app) {
248 // TODO : remove this check in the future(b/31479477). All apps should just
249 // request the appropriate permission for their use case since android Q.
250 if (app.applicationInfo != null) {
251 // Backward compatibility for b/114245686, on devices that launched before Q daemons
252 // and apps running as the system UID are exempted from this check.
253 if (app.applicationInfo.uid == SYSTEM_UID && getDeviceFirstSdkInt() < VERSION_Q) {
257 if (app.applicationInfo.targetSdkVersion < VERSION_Q
258 && isVendorApp(app.applicationInfo)) {
262 return hasPermission(app, CONNECTIVITY_INTERNAL)
263 || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
266 private boolean hasUseBackgroundNetworksPermission(PackageInfo app) {
267 // This function defines what it means to hold the permission to use
268 // background networks.
269 return hasPermission(app, CHANGE_NETWORK_STATE)
270 || hasPermission(app, NETWORK_STACK)
271 || hasRestrictedNetworkPermission(app);
274 public boolean hasUseBackgroundNetworksPermission(int uid) {
275 final String[] names = mPackageManager.getPackagesForUid(uid);
276 if (null == names || names.length == 0) return false;
278 // Only using the first package name. There may be multiple names if multiple
279 // apps share the same UID, but in that case they also share permissions so
280 // querying with any of the names will return the same results.
281 int userId = UserHandle.getUserId(uid);
282 final PackageInfo app = mPackageManager.getPackageInfoAsUser(
283 names[0], GET_PERMISSIONS, userId);
284 return hasUseBackgroundNetworksPermission(app);
285 } catch (NameNotFoundException e) {
287 loge("NameNotFoundException " + names[0], e);
292 private int[] toIntArray(Collection<Integer> list) {
293 int[] array = new int[list.size()];
295 for (Integer item : list) {
301 private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) {
302 List<Integer> network = new ArrayList<>();
303 List<Integer> system = new ArrayList<>();
304 for (Entry<Integer, Boolean> app : apps.entrySet()) {
305 List<Integer> list = app.getValue() ? system : network;
306 for (int user : users) {
307 list.add(UserHandle.getUid(user, app.getKey()));
312 mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network));
313 mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system));
315 mNetd.networkClearPermissionForUser(toIntArray(network));
316 mNetd.networkClearPermissionForUser(toIntArray(system));
318 } catch (RemoteException e) {
319 loge("Exception when updating permissions: " + e);
324 * Called when a user is added. See {link #ACTION_USER_ADDED}.
326 * @param user The integer userHandle of the added user. See {@link #EXTRA_USER_HANDLE}.
330 public synchronized void onUserAdded(int user) {
332 loge("Invalid user in onUserAdded: " + user);
337 Set<Integer> users = new HashSet<>();
339 update(users, mApps, true);
343 * Called when an user is removed. See {link #ACTION_USER_REMOVED}.
345 * @param user The integer userHandle of the removed user. See {@link #EXTRA_USER_HANDLE}.
349 public synchronized void onUserRemoved(int user) {
351 loge("Invalid user in onUserRemoved: " + user);
356 Set<Integer> users = new HashSet<>();
358 update(users, mApps, false);
362 protected Boolean highestPermissionForUid(Boolean currentPermission, String name) {
363 if (currentPermission == SYSTEM) {
364 return currentPermission;
367 final PackageInfo app = mPackageManager.getPackageInfo(name, GET_PERMISSIONS);
368 final boolean isNetwork = hasNetworkPermission(app);
369 final boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
370 if (isNetwork || hasRestrictedPermission) {
371 currentPermission = hasRestrictedPermission;
373 } catch (NameNotFoundException e) {
375 loge("NameNotFoundException " + name);
377 return currentPermission;
381 * Called when a package is added. See {link #ACTION_PACKAGE_ADDED}.
383 * @param packageName The name of the new package.
384 * @param uid The uid of the new package.
388 public synchronized void onPackageAdded(String packageName, int uid) {
389 // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
390 // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
391 final Boolean permission = highestPermissionForUid(mApps.get(uid), packageName);
392 if (permission != mApps.get(uid)) {
393 mApps.put(uid, permission);
395 Map<Integer, Boolean> apps = new HashMap<>();
396 apps.put(uid, permission);
397 update(mUsers, apps, true);
400 // If the newly-installed package falls within some VPN's uid range, update Netd with it.
401 // This needs to happen after the mApps update above, since removeBypassingUids() depends
402 // on mApps to check if the package can bypass VPN.
403 for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
404 if (UidRange.containsUid(vpn.getValue(), uid)) {
405 final Set<Integer> changedUids = new HashSet<>();
406 changedUids.add(uid);
407 removeBypassingUids(changedUids, /* vpnAppUid */ -1);
408 updateVpnUids(vpn.getKey(), changedUids, true);
411 mAllApps.add(UserHandle.getAppId(uid));
415 * Called when a package is removed. See {link #ACTION_PACKAGE_REMOVED}.
417 * @param uid containing the integer uid previously assigned to the package.
421 public synchronized void onPackageRemoved(int uid) {
422 // If the newly-removed package falls within some VPN's uid range, update Netd with it.
423 // This needs to happen before the mApps update below, since removeBypassingUids() depends
424 // on mApps to check if the package can bypass VPN.
425 for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
426 if (UidRange.containsUid(vpn.getValue(), uid)) {
427 final Set<Integer> changedUids = new HashSet<>();
428 changedUids.add(uid);
429 removeBypassingUids(changedUids, /* vpnAppUid */ -1);
430 updateVpnUids(vpn.getKey(), changedUids, false);
433 // If the package has been removed from all users on the device, clear it form mAllApps.
434 if (mPackageManager.getNameForUid(uid) == null) {
435 mAllApps.remove(UserHandle.getAppId(uid));
438 Map<Integer, Boolean> apps = new HashMap<>();
439 Boolean permission = null;
440 String[] packages = mPackageManager.getPackagesForUid(uid);
441 if (packages != null && packages.length > 0) {
442 for (String name : packages) {
443 permission = highestPermissionForUid(permission, name);
444 if (permission == SYSTEM) {
445 // An app with this UID still has the SYSTEM permission.
446 // Therefore, this UID must already have the SYSTEM permission.
452 if (permission == mApps.get(uid)) {
453 // The permissions of this UID have not changed. Nothing to do.
455 } else if (permission != null) {
456 mApps.put(uid, permission);
457 apps.put(uid, permission);
458 update(mUsers, apps, true);
461 apps.put(uid, NETWORK); // doesn't matter which permission we pick here
462 update(mUsers, apps, false);
466 private static int getNetdPermissionMask(String[] requestedPermissions,
467 int[] requestedPermissionsFlags) {
469 if (requestedPermissions == null || requestedPermissionsFlags == null) return permissions;
470 for (int i = 0; i < requestedPermissions.length; i++) {
471 if (requestedPermissions[i].equals(INTERNET)
472 && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
473 permissions |= INetd.PERMISSION_INTERNET;
475 if (requestedPermissions[i].equals(UPDATE_DEVICE_STATS)
476 && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
477 permissions |= INetd.PERMISSION_UPDATE_DEVICE_STATS;
483 private PackageInfo getPackageInfo(String packageName) {
485 PackageInfo app = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS
488 } catch (NameNotFoundException e) {
494 * Called when a new set of UID ranges are added to an active VPN network
496 * @param iface The active VPN network's interface name
497 * @param rangesToAdd The new UID ranges to be added to the network
498 * @param vpnAppUid The uid of the VPN app
500 public synchronized void onVpnUidRangesAdded(@NonNull String iface, Set<UidRange> rangesToAdd,
502 // Calculate the list of new app uids under the VPN due to the new UID ranges and update
503 // Netd about them. Because mAllApps only contains appIds instead of uids, the result might
504 // be an overestimation if an app is not installed on the user on which the VPN is running,
506 final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
507 removeBypassingUids(changedUids, vpnAppUid);
508 updateVpnUids(iface, changedUids, true);
509 if (mVpnUidRanges.containsKey(iface)) {
510 mVpnUidRanges.get(iface).addAll(rangesToAdd);
512 mVpnUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
517 * Called when a set of UID ranges are removed from an active VPN network
519 * @param iface The VPN network's interface name
520 * @param rangesToRemove Existing UID ranges to be removed from the VPN network
521 * @param vpnAppUid The uid of the VPN app
523 public synchronized void onVpnUidRangesRemoved(@NonNull String iface,
524 Set<UidRange> rangesToRemove, int vpnAppUid) {
525 // Calculate the list of app uids that are no longer under the VPN due to the removed UID
526 // ranges and update Netd about them.
527 final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
528 removeBypassingUids(changedUids, vpnAppUid);
529 updateVpnUids(iface, changedUids, false);
530 Set<UidRange> existingRanges = mVpnUidRanges.getOrDefault(iface, null);
531 if (existingRanges == null) {
532 loge("Attempt to remove unknown vpn uid Range iface = " + iface);
535 existingRanges.removeAll(rangesToRemove);
536 if (existingRanges.size() == 0) {
537 mVpnUidRanges.remove(iface);
542 * Compute the intersection of a set of UidRanges and appIds. Returns a set of uids
544 * 1. falls into one of the UidRange
545 * 2. matches one of the appIds
547 private Set<Integer> intersectUids(Set<UidRange> ranges, Set<Integer> appIds) {
548 Set<Integer> result = new HashSet<>();
549 for (UidRange range : ranges) {
550 for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) {
551 for (int appId : appIds) {
552 final int uid = UserHandle.getUid(userId, appId);
553 if (range.contains(uid)) {
563 * Remove all apps which can elect to bypass the VPN from the list of uids
565 * An app can elect to bypass the VPN if it hold SYSTEM permission, or if its the active VPN
568 * @param uids The list of uids to operate on
569 * @param vpnAppUid The uid of the VPN app
571 private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
572 uids.remove(vpnAppUid);
573 uids.removeIf(uid -> mApps.getOrDefault(uid, NETWORK) == SYSTEM);
577 * Update netd about the list of uids that are under an active VPN connection which they cannot
580 * This is to instruct netd to set up appropriate filtering rules for these uids, such that they
581 * can only receive ingress packets from the VPN's tunnel interface (and loopback).
583 * @param iface the interface name of the active VPN connection
584 * @param add {@code true} if the uids are to be added to the interface, {@code false} if they
585 * are to be removed from the interface.
587 private void updateVpnUids(String iface, Set<Integer> uids, boolean add) {
588 if (uids.size() == 0) {
593 mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
595 mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
597 } catch (ServiceSpecificException e) {
598 // Silently ignore exception when device does not support eBPF, otherwise just log
599 // the exception and do not crash
600 if (e.errorCode != OsConstants.EOPNOTSUPP) {
601 loge("Exception when updating permissions: ", e);
603 } catch (RemoteException e) {
604 loge("Exception when updating permissions: ", e);
609 * Called by PackageListObserver when a package is installed/uninstalled. Send the updated
610 * permission information to netd.
612 * @param uid the app uid of the package installed
613 * @param permissions the permissions the app requested and netd cares about.
618 void sendPackagePermissionsForUid(int uid, int permissions) {
619 SparseIntArray netdPermissionsAppIds = new SparseIntArray();
620 netdPermissionsAppIds.put(uid, permissions);
621 sendPackagePermissionsToNetd(netdPermissionsAppIds);
625 * Called by packageManagerService to send IPC to netd. Grant or revoke the INTERNET
626 * and/or UPDATE_DEVICE_STATS permission of the uids in array.
628 * @param netdPermissionsAppIds integer pairs of uids and the permission granted to it. If the
629 * permission is 0, revoke all permissions of that uid.
634 void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
636 Log.e(TAG, "Failed to get the netd service");
639 ArrayList<Integer> allPermissionAppIds = new ArrayList<>();
640 ArrayList<Integer> internetPermissionAppIds = new ArrayList<>();
641 ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
642 ArrayList<Integer> noPermissionAppIds = new ArrayList<>();
643 ArrayList<Integer> uninstalledAppIds = new ArrayList<>();
644 for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
645 int permissions = netdPermissionsAppIds.valueAt(i);
646 switch(permissions) {
647 case (INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS):
648 allPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
650 case INetd.PERMISSION_INTERNET:
651 internetPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
653 case INetd.PERMISSION_UPDATE_DEVICE_STATS:
654 updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
656 case INetd.PERMISSION_NONE:
657 noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
659 case INetd.PERMISSION_UNINSTALLED:
660 uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
662 Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
663 + netdPermissionsAppIds.keyAt(i));
667 // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
668 if (allPermissionAppIds.size() != 0) {
669 mNetd.trafficSetNetPermForUids(
670 INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
671 ArrayUtils.convertToIntArray(allPermissionAppIds));
673 if (internetPermissionAppIds.size() != 0) {
674 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
675 ArrayUtils.convertToIntArray(internetPermissionAppIds));
677 if (updateStatsPermissionAppIds.size() != 0) {
678 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
679 ArrayUtils.convertToIntArray(updateStatsPermissionAppIds));
681 if (noPermissionAppIds.size() != 0) {
682 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_NONE,
683 ArrayUtils.convertToIntArray(noPermissionAppIds));
685 if (uninstalledAppIds.size() != 0) {
686 mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED,
687 ArrayUtils.convertToIntArray(uninstalledAppIds));
689 } catch (RemoteException e) {
690 Log.e(TAG, "Pass appId list of special permission failed." + e);
694 /** Should only be used by unit tests */
696 public Set<UidRange> getVpnUidRanges(String iface) {
697 return mVpnUidRanges.get(iface);
700 /** Dump info to dumpsys */
701 public void dump(IndentingPrintWriter pw) {
702 pw.println("Interface filtering rules:");
704 for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
705 pw.println("Interface: " + vpn.getKey());
706 pw.println("UIDs: " + vpn.getValue().toString());
712 private static void log(String s) {
718 private static void loge(String s) {
722 private static void loge(String s, Throwable e) {