Loading packages/Tethering/src/android/net/ip/IpServer.java +14 −1 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ import androidx.annotation.Nullable; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.PrivateAddressCoordinator; import java.io.IOException; Loading Loading @@ -225,6 +226,8 @@ public class IpServer extends StateMachine { private final SharedLog mLog; private final INetd mNetd; @NonNull private final BpfCoordinator mBpfCoordinator; private final Callback mCallback; private final InterfaceController mInterfaceCtrl; private final PrivateAddressCoordinator mPrivateAddressCoordinator; Loading Loading @@ -314,11 +317,13 @@ public class IpServer extends StateMachine { // object. It helps to reduce the arguments of the constructor. public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload, INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload, PrivateAddressCoordinator addressCoordinator, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNetd = netd; mBpfCoordinator = coordinator; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; Loading Loading @@ -754,6 +759,14 @@ public class IpServer extends StateMachine { } upstreamIfindex = mDeps.getIfindex(upstreamIface); // Add upstream index to name mapping for the tether stats usage in the coordinator. // Although this mapping could be added by both class Tethering and IpServer, adding // mapping from IpServer guarantees that the mapping is added before the adding // forwarding rules. That is because there are different state machines in both // classes. It is hard to guarantee the link property update order between multiple // state machines. mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface); } // If v6only is null, we pass in null to setRaParams(), which handles Loading packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java 0 → 100644 +280 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.networkstack.tethering; import static android.net.NetworkStats.DEFAULT_NETWORK_NO; import static android.net.NetworkStats.METERED_NO; import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; import android.app.usage.NetworkStatsManager; import android.net.INetd; import android.net.NetworkStats; import android.net.NetworkStats.Entry; import android.net.TetherStatsParcel; import android.net.netstats.provider.NetworkStatsProvider; import android.net.util.SharedLog; import android.net.util.TetheringUtils.ForwardedStats; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * This coordinator is responsible for providing BPF offload relevant functionality. * - Get tethering stats. * * @hide */ public class BpfCoordinator { private static final String TAG = BpfCoordinator.class.getSimpleName(); // TODO: Make it customizable. private static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; private enum StatsType { STATS_PER_IFACE, STATS_PER_UID, } @NonNull private final Handler mHandler; @NonNull private final INetd mNetd; @NonNull private final SharedLog mLog; @NonNull private final Dependencies mDeps; @Nullable private final BpfTetherStatsProvider mStatsProvider; private boolean mStarted = false; // Maps upstream interface index to offloaded traffic statistics. // Always contains the latest total bytes/packets, since each upstream was started, received // from the BPF maps for each interface. private SparseArray<ForwardedStats> mStats = new SparseArray<>(); // Maps upstream interface index to interface names. // Store all interface name since boot. Used for lookup what interface name it is from the // tether stats got from netd because netd reports interface index to present an interface. // TODO: Remove the unused interface name. private SparseArray<String> mInterfaceNames = new SparseArray<>(); // Runnable that used by scheduling next polling of stats. private final Runnable mScheduledPollingTask = () -> { updateForwardedStatsFromNetd(); maybeSchedulePollingStats(); }; static class Dependencies { int getPerformPollInterval() { // TODO: Consider make this configurable. return DEFAULT_PERFORM_POLL_INTERVAL_MS; } } BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd, @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) { mHandler = handler; mNetd = netd; mLog = log.forSubComponent(TAG); BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); try { nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider); } catch (RuntimeException e) { // TODO: Perhaps not allow to use BPF offload because the reregistration failure // implied that no data limit could be applies on a metered upstream if any. Log.wtf(TAG, "Cannot register offload stats provider: " + e); provider = null; } mStatsProvider = provider; mDeps = deps; } /** * Start BPF tethering offload stats polling when the first upstream is started. * Note that this can be only called on handler thread. * TODO: Perhaps check BPF support before starting. * TODO: Start the stats polling only if there is any client on the downstream. */ public void start() { if (mStarted) return; mStarted = true; maybeSchedulePollingStats(); mLog.i("BPF tethering coordinator started"); } /** * Stop BPF tethering offload stats polling and cleanup upstream parameters. * Note that this can be only called on handler thread. */ public void stop() { if (!mStarted) return; // Stop scheduled polling tasks and poll the latest stats from BPF maps. if (mHandler.hasCallbacks(mScheduledPollingTask)) { mHandler.removeCallbacks(mScheduledPollingTask); } updateForwardedStatsFromNetd(); mStarted = false; mLog.i("BPF tethering coordinator stopped"); } /** * Add upstream name to lookup table. The lookup table is used for tether stats interface name * lookup because the netd only reports interface index in BPF tether stats but the service * expects the interface name in NetworkStats object. * Note that this can be only called on handler thread. */ public void addUpstreamNameToLookupTable(int upstreamIfindex, String upstreamIface) { if (upstreamIfindex == 0) return; // The same interface index to name mapping may be added by different IpServer objects or // re-added by reconnection on the same upstream interface. Ignore the duplicate one. final String iface = mInterfaceNames.get(upstreamIfindex); if (iface == null) { mInterfaceNames.put(upstreamIfindex, upstreamIface); } else if (iface != upstreamIface) { Log.wtf(TAG, "The upstream interface name " + upstreamIface + " is different from the existing interface name " + iface + " for index " + upstreamIfindex); } } /** * A BPF tethering stats provider to provide network statistics to the system. * Note that this class's data may only be accessed on the handler thread. */ private class BpfTetherStatsProvider extends NetworkStatsProvider { // The offloaded traffic statistics per interface that has not been reported since the // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams // and has pending tether stats delta are included in this NetworkStats object. private NetworkStats mIfaceStats = new NetworkStats(0L, 0); // The same stats as above, but counts network stats per uid. private NetworkStats mUidStats = new NetworkStats(0L, 0); @Override public void onRequestStatsUpdate(int token) { mHandler.post(() -> pushTetherStats()); } @Override public void onSetAlert(long quotaBytes) { // no-op } @Override public void onSetLimit(@NonNull String iface, long quotaBytes) { // no-op } private void pushTetherStats() { try { // The token is not used for now. See b/153606961. notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats); // Clear the accumulated tether stats delta after reported. Note that create a new // empty object because NetworkStats#clear is @hide. mIfaceStats = new NetworkStats(0L, 0); mUidStats = new NetworkStats(0L, 0); } catch (RuntimeException e) { mLog.e("Cannot report network stats: ", e); } } private void accumulateDiff(@NonNull NetworkStats ifaceDiff, @NonNull NetworkStats uidDiff) { mIfaceStats = mIfaceStats.add(ifaceDiff); mUidStats = mUidStats.add(uidDiff); } } @NonNull private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex, @NonNull ForwardedStats diff) { NetworkStats stats = new NetworkStats(0L, 0); final String iface = mInterfaceNames.get(ifIndex); if (iface == null) { // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd. // For now, netd may add the empty stats for the upstream which is not monitored by // the coordinator. Silently ignore it. return stats; } final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked. return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets, diff.txBytes, diff.txPackets, 0L /* operations */)); } private void updateForwardedStatsFromNetd() { final TetherStatsParcel[] tetherStatsList; try { // The reported tether stats are total data usage for all currently-active upstream // interfaces since tethering start. tetherStatsList = mNetd.tetherOffloadGetStats(); } catch (RemoteException | ServiceSpecificException e) { mLog.e("Problem fetching tethering stats: ", e); return; } for (TetherStatsParcel tetherStats : tetherStatsList) { final Integer ifIndex = tetherStats.ifIndex; final ForwardedStats curr = new ForwardedStats(tetherStats); final ForwardedStats base = mStats.get(ifIndex); final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr; // Update the local cache for counting tether stats delta. mStats.put(ifIndex, curr); // Update the accumulated tether stats delta to the stats provider for the service // querying. if (mStatsProvider != null) { try { mStatsProvider.accumulateDiff( buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff), buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff)); } catch (ArrayIndexOutOfBoundsException e) { Log.wtf("Fail to update the accumulated stats delta for interface index " + ifIndex + " : ", e); } } } } private void maybeSchedulePollingStats() { if (!mStarted) return; if (mHandler.hasCallbacks(mScheduledPollingTask)) { mHandler.removeCallbacks(mScheduledPollingTask); } mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval()); } } packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +8 −1 Original line number Diff line number Diff line Loading @@ -232,6 +232,7 @@ public class Tethering { private final TetheringThreadExecutor mExecutor; private final TetheringNotificationUpdater mNotificationUpdater; private final UserManager mUserManager; private final BpfCoordinator mBpfCoordinator; private final PrivateAddressCoordinator mPrivateAddressCoordinator; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; // All the usage of mTetheringEventCallback should run in the same thread. Loading Loading @@ -284,6 +285,8 @@ public class Tethering { mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new LinkedHashSet<>(); mBpfCoordinator = mDeps.getBpfCoordinator( mHandler, mNetd, mLog, new BpfCoordinator.Dependencies()); IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); Loading Loading @@ -1704,6 +1707,9 @@ public class Tethering { chooseUpstreamType(true); mTryCell = false; } // TODO: Check the upstream interface if it is managed by BPF offload. mBpfCoordinator.start(); } @Override Loading @@ -1716,6 +1722,7 @@ public class Tethering { mTetherUpstream = null; reportUpstreamChanged(null); } mBpfCoordinator.stop(); } private boolean updateUpstreamWanted() { Loading Loading @@ -2341,7 +2348,7 @@ public class Tethering { mLog.log("adding TetheringInterfaceStateMachine for: " + iface); final TetherState tetherState = new TetherState( new IpServer(iface, mLooper, interfaceType, mLog, mNetd, new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, makeControlCallback(), mConfig.enableLegacyDhcpServer, mConfig.enableBpfOffload, mPrivateAddressCoordinator, mDeps.getIpServerDependencies())); Loading packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +11 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,17 @@ import java.util.ArrayList; * @hide */ public abstract class TetheringDependencies { /** * Get a reference to the BpfCoordinator to be used by tethering. */ public @NonNull BpfCoordinator getBpfCoordinator( @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log, @NonNull BpfCoordinator.Dependencies deps) { final NetworkStatsManager statsManager = (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); return new BpfCoordinator(handler, netd, statsManager, log, deps); } /** * Get a reference to the offload hardware interface to be used by tethering. */ Loading packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +5 −3 Original line number Diff line number Diff line Loading @@ -87,6 +87,7 @@ import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.PrivateAddressCoordinator; import org.junit.Before; Loading Loading @@ -126,6 +127,7 @@ public class IpServerTest { private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24"); @Mock private INetd mNetd; @Mock private BpfCoordinator mBpfCoordinator; @Mock private IpServer.Callback mCallback; @Mock private SharedLog mSharedLog; @Mock private IDhcpServer mDhcpServer; Loading Loading @@ -179,7 +181,7 @@ public class IpServerTest { neighborCaptor.capture()); mIpServer = new IpServer( IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, mDependencies); mIpServer.start(); mNeighborEventConsumer = neighborCaptor.getValue(); Loading Loading @@ -222,8 +224,8 @@ public class IpServerTest { when(mDependencies.getIpNeighborMonitor(any(), any(), any())) .thenReturn(mIpNeighborMonitor); mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies); mNetd, mBpfCoordinator, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( Loading Loading
packages/Tethering/src/android/net/ip/IpServer.java +14 −1 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ import androidx.annotation.Nullable; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.PrivateAddressCoordinator; import java.io.IOException; Loading Loading @@ -225,6 +226,8 @@ public class IpServer extends StateMachine { private final SharedLog mLog; private final INetd mNetd; @NonNull private final BpfCoordinator mBpfCoordinator; private final Callback mCallback; private final InterfaceController mInterfaceCtrl; private final PrivateAddressCoordinator mPrivateAddressCoordinator; Loading Loading @@ -314,11 +317,13 @@ public class IpServer extends StateMachine { // object. It helps to reduce the arguments of the constructor. public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload, INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload, PrivateAddressCoordinator addressCoordinator, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNetd = netd; mBpfCoordinator = coordinator; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; Loading Loading @@ -754,6 +759,14 @@ public class IpServer extends StateMachine { } upstreamIfindex = mDeps.getIfindex(upstreamIface); // Add upstream index to name mapping for the tether stats usage in the coordinator. // Although this mapping could be added by both class Tethering and IpServer, adding // mapping from IpServer guarantees that the mapping is added before the adding // forwarding rules. That is because there are different state machines in both // classes. It is hard to guarantee the link property update order between multiple // state machines. mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface); } // If v6only is null, we pass in null to setRaParams(), which handles Loading
packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java 0 → 100644 +280 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.networkstack.tethering; import static android.net.NetworkStats.DEFAULT_NETWORK_NO; import static android.net.NetworkStats.METERED_NO; import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; import android.app.usage.NetworkStatsManager; import android.net.INetd; import android.net.NetworkStats; import android.net.NetworkStats.Entry; import android.net.TetherStatsParcel; import android.net.netstats.provider.NetworkStatsProvider; import android.net.util.SharedLog; import android.net.util.TetheringUtils.ForwardedStats; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * This coordinator is responsible for providing BPF offload relevant functionality. * - Get tethering stats. * * @hide */ public class BpfCoordinator { private static final String TAG = BpfCoordinator.class.getSimpleName(); // TODO: Make it customizable. private static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; private enum StatsType { STATS_PER_IFACE, STATS_PER_UID, } @NonNull private final Handler mHandler; @NonNull private final INetd mNetd; @NonNull private final SharedLog mLog; @NonNull private final Dependencies mDeps; @Nullable private final BpfTetherStatsProvider mStatsProvider; private boolean mStarted = false; // Maps upstream interface index to offloaded traffic statistics. // Always contains the latest total bytes/packets, since each upstream was started, received // from the BPF maps for each interface. private SparseArray<ForwardedStats> mStats = new SparseArray<>(); // Maps upstream interface index to interface names. // Store all interface name since boot. Used for lookup what interface name it is from the // tether stats got from netd because netd reports interface index to present an interface. // TODO: Remove the unused interface name. private SparseArray<String> mInterfaceNames = new SparseArray<>(); // Runnable that used by scheduling next polling of stats. private final Runnable mScheduledPollingTask = () -> { updateForwardedStatsFromNetd(); maybeSchedulePollingStats(); }; static class Dependencies { int getPerformPollInterval() { // TODO: Consider make this configurable. return DEFAULT_PERFORM_POLL_INTERVAL_MS; } } BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd, @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) { mHandler = handler; mNetd = netd; mLog = log.forSubComponent(TAG); BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); try { nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider); } catch (RuntimeException e) { // TODO: Perhaps not allow to use BPF offload because the reregistration failure // implied that no data limit could be applies on a metered upstream if any. Log.wtf(TAG, "Cannot register offload stats provider: " + e); provider = null; } mStatsProvider = provider; mDeps = deps; } /** * Start BPF tethering offload stats polling when the first upstream is started. * Note that this can be only called on handler thread. * TODO: Perhaps check BPF support before starting. * TODO: Start the stats polling only if there is any client on the downstream. */ public void start() { if (mStarted) return; mStarted = true; maybeSchedulePollingStats(); mLog.i("BPF tethering coordinator started"); } /** * Stop BPF tethering offload stats polling and cleanup upstream parameters. * Note that this can be only called on handler thread. */ public void stop() { if (!mStarted) return; // Stop scheduled polling tasks and poll the latest stats from BPF maps. if (mHandler.hasCallbacks(mScheduledPollingTask)) { mHandler.removeCallbacks(mScheduledPollingTask); } updateForwardedStatsFromNetd(); mStarted = false; mLog.i("BPF tethering coordinator stopped"); } /** * Add upstream name to lookup table. The lookup table is used for tether stats interface name * lookup because the netd only reports interface index in BPF tether stats but the service * expects the interface name in NetworkStats object. * Note that this can be only called on handler thread. */ public void addUpstreamNameToLookupTable(int upstreamIfindex, String upstreamIface) { if (upstreamIfindex == 0) return; // The same interface index to name mapping may be added by different IpServer objects or // re-added by reconnection on the same upstream interface. Ignore the duplicate one. final String iface = mInterfaceNames.get(upstreamIfindex); if (iface == null) { mInterfaceNames.put(upstreamIfindex, upstreamIface); } else if (iface != upstreamIface) { Log.wtf(TAG, "The upstream interface name " + upstreamIface + " is different from the existing interface name " + iface + " for index " + upstreamIfindex); } } /** * A BPF tethering stats provider to provide network statistics to the system. * Note that this class's data may only be accessed on the handler thread. */ private class BpfTetherStatsProvider extends NetworkStatsProvider { // The offloaded traffic statistics per interface that has not been reported since the // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams // and has pending tether stats delta are included in this NetworkStats object. private NetworkStats mIfaceStats = new NetworkStats(0L, 0); // The same stats as above, but counts network stats per uid. private NetworkStats mUidStats = new NetworkStats(0L, 0); @Override public void onRequestStatsUpdate(int token) { mHandler.post(() -> pushTetherStats()); } @Override public void onSetAlert(long quotaBytes) { // no-op } @Override public void onSetLimit(@NonNull String iface, long quotaBytes) { // no-op } private void pushTetherStats() { try { // The token is not used for now. See b/153606961. notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats); // Clear the accumulated tether stats delta after reported. Note that create a new // empty object because NetworkStats#clear is @hide. mIfaceStats = new NetworkStats(0L, 0); mUidStats = new NetworkStats(0L, 0); } catch (RuntimeException e) { mLog.e("Cannot report network stats: ", e); } } private void accumulateDiff(@NonNull NetworkStats ifaceDiff, @NonNull NetworkStats uidDiff) { mIfaceStats = mIfaceStats.add(ifaceDiff); mUidStats = mUidStats.add(uidDiff); } } @NonNull private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex, @NonNull ForwardedStats diff) { NetworkStats stats = new NetworkStats(0L, 0); final String iface = mInterfaceNames.get(ifIndex); if (iface == null) { // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd. // For now, netd may add the empty stats for the upstream which is not monitored by // the coordinator. Silently ignore it. return stats; } final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked. return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets, diff.txBytes, diff.txPackets, 0L /* operations */)); } private void updateForwardedStatsFromNetd() { final TetherStatsParcel[] tetherStatsList; try { // The reported tether stats are total data usage for all currently-active upstream // interfaces since tethering start. tetherStatsList = mNetd.tetherOffloadGetStats(); } catch (RemoteException | ServiceSpecificException e) { mLog.e("Problem fetching tethering stats: ", e); return; } for (TetherStatsParcel tetherStats : tetherStatsList) { final Integer ifIndex = tetherStats.ifIndex; final ForwardedStats curr = new ForwardedStats(tetherStats); final ForwardedStats base = mStats.get(ifIndex); final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr; // Update the local cache for counting tether stats delta. mStats.put(ifIndex, curr); // Update the accumulated tether stats delta to the stats provider for the service // querying. if (mStatsProvider != null) { try { mStatsProvider.accumulateDiff( buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff), buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff)); } catch (ArrayIndexOutOfBoundsException e) { Log.wtf("Fail to update the accumulated stats delta for interface index " + ifIndex + " : ", e); } } } } private void maybeSchedulePollingStats() { if (!mStarted) return; if (mHandler.hasCallbacks(mScheduledPollingTask)) { mHandler.removeCallbacks(mScheduledPollingTask); } mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval()); } }
packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +8 −1 Original line number Diff line number Diff line Loading @@ -232,6 +232,7 @@ public class Tethering { private final TetheringThreadExecutor mExecutor; private final TetheringNotificationUpdater mNotificationUpdater; private final UserManager mUserManager; private final BpfCoordinator mBpfCoordinator; private final PrivateAddressCoordinator mPrivateAddressCoordinator; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; // All the usage of mTetheringEventCallback should run in the same thread. Loading Loading @@ -284,6 +285,8 @@ public class Tethering { mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new LinkedHashSet<>(); mBpfCoordinator = mDeps.getBpfCoordinator( mHandler, mNetd, mLog, new BpfCoordinator.Dependencies()); IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); Loading Loading @@ -1704,6 +1707,9 @@ public class Tethering { chooseUpstreamType(true); mTryCell = false; } // TODO: Check the upstream interface if it is managed by BPF offload. mBpfCoordinator.start(); } @Override Loading @@ -1716,6 +1722,7 @@ public class Tethering { mTetherUpstream = null; reportUpstreamChanged(null); } mBpfCoordinator.stop(); } private boolean updateUpstreamWanted() { Loading Loading @@ -2341,7 +2348,7 @@ public class Tethering { mLog.log("adding TetheringInterfaceStateMachine for: " + iface); final TetherState tetherState = new TetherState( new IpServer(iface, mLooper, interfaceType, mLog, mNetd, new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, makeControlCallback(), mConfig.enableLegacyDhcpServer, mConfig.enableBpfOffload, mPrivateAddressCoordinator, mDeps.getIpServerDependencies())); Loading
packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +11 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,17 @@ import java.util.ArrayList; * @hide */ public abstract class TetheringDependencies { /** * Get a reference to the BpfCoordinator to be used by tethering. */ public @NonNull BpfCoordinator getBpfCoordinator( @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log, @NonNull BpfCoordinator.Dependencies deps) { final NetworkStatsManager statsManager = (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); return new BpfCoordinator(handler, netd, statsManager, log, deps); } /** * Get a reference to the offload hardware interface to be used by tethering. */ Loading
packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +5 −3 Original line number Diff line number Diff line Loading @@ -87,6 +87,7 @@ import android.text.TextUtils; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.PrivateAddressCoordinator; import org.junit.Before; Loading Loading @@ -126,6 +127,7 @@ public class IpServerTest { private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24"); @Mock private INetd mNetd; @Mock private BpfCoordinator mBpfCoordinator; @Mock private IpServer.Callback mCallback; @Mock private SharedLog mSharedLog; @Mock private IDhcpServer mDhcpServer; Loading Loading @@ -179,7 +181,7 @@ public class IpServerTest { neighborCaptor.capture()); mIpServer = new IpServer( IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, mDependencies); mIpServer.start(); mNeighborEventConsumer = neighborCaptor.getValue(); Loading Loading @@ -222,8 +224,8 @@ public class IpServerTest { when(mDependencies.getIpNeighborMonitor(any(), any(), any())) .thenReturn(mIpNeighborMonitor); mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies); mNetd, mBpfCoordinator, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( Loading