diff options
7 files changed, 186 insertions, 10 deletions
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index eef658e..7046008 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -83,6 +83,12 @@ interface IConnectivityManager String[] getTetheredIfaces(); + /** + * Return list of interface pairs that are actively tethered. Even indexes are + * remote interface, and odd indexes are corresponding local interfaces. + */ + String[] getTetheredIfacePairs(); + String[] getTetheringErroredIfaces(); String[] getTetherableUsbRegexs(); diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 47cfa73..18eb9f6 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -53,25 +53,33 @@ public class TrafficStats { public static final int UID_REMOVED = -4; /** + * Special UID value used when collecting {@link NetworkStatsHistory} for + * tethering traffic. + * + * @hide + */ + public static final int UID_TETHERING = -5; + + /** * Default tag value for {@link DownloadManager} traffic. * * @hide */ - public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFF0001; + public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFFFF01; /** * Default tag value for {@link MediaPlayer} traffic. * * @hide */ - public static final int TAG_SYSTEM_MEDIA = 0xFFFF0002; + public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02; /** * Default tag value for {@link BackupManager} traffic. * * @hide */ - public static final int TAG_SYSTEM_BACKUP = 0xFFFF0003; + public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03; /** * Snapshot of {@link NetworkStats} when the currently active profiling @@ -90,6 +98,10 @@ public class TrafficStats { * <p> * Changes only take effect during subsequent calls to * {@link #tagSocket(Socket)}. + * <p> + * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and + * used internally by system services like {@link DownloadManager} when + * performing traffic on behalf of an application. */ public static void setThreadStatsTag(int tag) { NetworkManagementSocketTagger.setThreadSocketStatsTag(tag); diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 4e5645d..66373fe 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -231,6 +231,13 @@ interface INetworkManagementService NetworkStats getNetworkStatsUidDetail(int uid); /** + * Return summary of network statistics for the requested pairs of + * tethering interfaces. Even indexes are remote interface, and odd + * indexes are corresponding local interfaces. + */ + NetworkStats getNetworkStatsTethering(in String[] ifacePairs); + + /** * Set quota for an interface. */ void setInterfaceQuota(String iface, long quotaBytes); diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 2348d76..e0a2adc 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -2394,6 +2394,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mTethering.getTetheredIfaces(); } + @Override + public String[] getTetheredIfacePairs() { + enforceTetherAccessPermission(); + return mTethering.getTetheredIfacePairs(); + } + public String[] getTetheringErroredIfaces() { enforceTetherAccessPermission(); return mTethering.getErroredIfaces(); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index c517965..0cffb15 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -123,6 +123,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub public static final int InterfaceTxCounterResult = 217; public static final int InterfaceRxThrottleResult = 218; public static final int InterfaceTxThrottleResult = 219; + public static final int QuotaCounterResult = 220; + public static final int TetheringStatsResult = 221; public static final int InterfaceChange = 600; public static final int BandwidthControl = 601; @@ -1443,6 +1445,73 @@ public class NetworkManagementService extends INetworkManagementService.Stub return stats; } + @Override + public NetworkStats getNetworkStatsTethering(String[] ifacePairs) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + + if (ifacePairs.length % 2 != 0) { + throw new IllegalArgumentException( + "unexpected ifacePairs; length=" + ifacePairs.length); + } + + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); + for (int i = 0; i < ifacePairs.length; i += 2) { + final String ifaceIn = ifacePairs[i]; + final String ifaceOut = ifacePairs[i + 1]; + if (ifaceIn != null && ifaceOut != null) { + stats.combineValues(getNetworkStatsTethering(ifaceIn, ifaceOut)); + } + } + return stats; + } + + private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) { + final StringBuilder command = new StringBuilder(); + command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut); + + final String rsp; + try { + rsp = mConnector.doCommand(command.toString()).get(0); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon", e); + } + + final String[] tok = rsp.split(" "); + /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */ + if (tok.length != 7) { + throw new IllegalStateException("Native daemon returned unexpected result: " + rsp); + } + + final int code; + try { + code = Integer.parseInt(tok[0]); + } catch (NumberFormatException e) { + throw new IllegalStateException( + "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut); + } + if (code != NetdResponseCode.TetheringStatsResult) { + throw new IllegalStateException( + "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut); + } + + try { + final NetworkStats.Entry entry = new NetworkStats.Entry(); + entry.iface = ifaceIn; + entry.uid = UID_ALL; + entry.set = SET_DEFAULT; + entry.tag = TAG_NONE; + entry.rxBytes = Long.parseLong(tok[3]); + entry.rxPackets = Long.parseLong(tok[4]); + entry.txBytes = Long.parseLong(tok[5]); + entry.txPackets = Long.parseLong(tok[6]); + return entry; + } catch (NumberFormatException e) { + throw new IllegalStateException( + "problem parsing tethering stats for " + ifaceIn + " " + ifaceOut + ": " + e); + } + } + public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index ae8b89d..5286824 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -19,7 +19,6 @@ package com.android.server.connectivity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.bluetooth.BluetoothPan; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -28,15 +27,14 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; -import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; +import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkUtils; import android.os.Binder; -import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.INetworkManagementService; @@ -51,6 +49,7 @@ import com.android.internal.telephony.Phone; import com.android.internal.util.IState; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.google.android.collect.Lists; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -59,8 +58,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.Set; + /** * @hide * @@ -68,7 +67,6 @@ import java.util.Set; * * TODO - look for parent classes and code sharing */ - public class Tethering extends INetworkManagementEventObserver.Stub { private Context mContext; @@ -629,6 +627,19 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return retVal; } + public String[] getTetheredIfacePairs() { + final ArrayList<String> list = Lists.newArrayList(); + synchronized (mIfaces) { + for (TetherInterfaceSM sm : mIfaces.values()) { + if (sm.isTethered()) { + list.add(sm.mMyUpstreamIfaceName); + list.add(sm.mIfaceName); + } + } + } + return list.toArray(new String[list.size()]); + } + public String[] getTetherableIfaces() { ArrayList<String> list = new ArrayList<String>(); synchronized (mIfaces) { diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 3673eab..c180b72 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -25,6 +25,7 @@ import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.SET_DEFAULT; @@ -34,6 +35,7 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.TrafficStats.UID_REMOVED; +import static android.net.TrafficStats.UID_TETHERING; import static android.provider.Settings.Secure.NETSTATS_FORCE_COMPLETE_POLL; import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION; import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY; @@ -68,6 +70,7 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.TrafficStats; import android.os.Binder; import android.os.Environment; import android.os.Handler; @@ -89,6 +92,7 @@ import android.util.TrustedTime; import com.android.internal.os.AtomicFile; import com.android.internal.util.Objects; import com.android.server.EventLogTags; +import com.android.server.connectivity.Tethering; import com.google.android.collect.Lists; import com.google.android.collect.Maps; import com.google.android.collect.Sets; @@ -134,11 +138,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Flags to control detail level of poll event. */ private static final int FLAG_POLL_NETWORK = 0x1; private static final int FLAG_POLL_UID = 0x2; + private static final int FLAG_POLL_TETHER = 0x3; private static final int FLAG_PERSIST_NETWORK = 0x10; private static final int FLAG_PERSIST_UID = 0x20; private static final int FLAG_FORCE_PERSIST = 0x100; - private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID; + private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID | FLAG_POLL_TETHER; private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID; private final Context mContext; @@ -195,6 +200,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private NetworkStats mLastPollNetworkSnapshot; private NetworkStats mLastPollUidSnapshot; private NetworkStats mLastPollOperationsSnapshot; + private NetworkStats mLastPollTetherSnapshot; private NetworkStats mLastPersistNetworkSnapshot; private NetworkStats mLastPersistUidSnapshot; @@ -258,6 +264,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE); mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler); + // watch for tethering changes + final IntentFilter tetherFilter = new IntentFilter(ACTION_TETHER_STATE_CHANGED); + mContext.registerReceiver(mTetherReceiver, tetherFilter, CONNECTIVITY_INTERNAL, mHandler); + // listen for periodic polling events final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL); mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler); @@ -543,6 +553,18 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } }; + /** + * Receiver that watches for {@link Tethering} to claim interface pairs. + */ + private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and verified CONNECTIVITY_INTERNAL + // permission above. + performPoll(FLAG_POLL_TETHER); + } + }; + private BroadcastReceiver mPollReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -686,12 +708,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { boolean pollNetwork = (flags & FLAG_POLL_NETWORK) != 0; boolean pollUid = (flags & FLAG_POLL_UID) != 0; + boolean pollTether = (flags & FLAG_POLL_TETHER) != 0; // when complete poll requested, any partial poll enables everything final boolean forceCompletePoll = mSettings.getForceCompletePoll(); - if (forceCompletePoll && (pollNetwork || pollUid)) { + if (forceCompletePoll && (pollNetwork || pollUid || pollTether)) { pollNetwork = true; pollUid = true; + pollTether = true; } final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0; @@ -723,6 +747,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + if (pollTether) { + final String[] ifacePairs = mConnManager.getTetheredIfacePairs(); + final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering( + ifacePairs); + performTetherPollLocked(tetherSnapshot, currentTime); + + // persisted during normal UID cycle below + } + if (pollUid) { final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); performUidPollLocked(uidSnapshot, currentTime); @@ -849,6 +882,38 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** + * Update {@link #mUidStats} historical usage for + * {@link TrafficStats#UID_TETHERING} based on tethering statistics. + */ + private void performTetherPollLocked(NetworkStats tetherSnapshot, long currentTime) { + ensureUidStatsLoadedLocked(); + + final NetworkStats delta = computeStatsDelta( + mLastPollTetherSnapshot, tetherSnapshot, false); + final long timeStart = currentTime - delta.getElapsedRealtime(); + + NetworkStats.Entry entry = null; + for (int i = 0; i < delta.size(); i++) { + entry = delta.getValues(i, entry); + final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); + if (ident == null) { + if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 + || entry.txPackets > 0) { + Log.w(TAG, "dropping tether delta from unknown iface: " + entry); + } + continue; + } + + final NetworkStatsHistory history = findOrCreateUidStatsLocked( + ident, UID_TETHERING, SET_DEFAULT, TAG_NONE); + history.recordData(timeStart, currentTime, entry); + } + + // normal UID poll will trim any history beyond max + mLastPollTetherSnapshot = tetherSnapshot; + } + + /** * Sample recent statistics summary into {@link EventLog}. */ private void performSample() { |