Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4ca0ba8f authored by Lorenzo Colitti's avatar Lorenzo Colitti
Browse files

Fetch tethering offload stats.

Make tethering offload register an ITetheringStatsProvider and
fetch tethering stats from the hardware.

Currently we fetch stats in the following cases:

1. Just after changing upstreams, we fetch stats from the
   previous upstream.
2. When we are polled by NetworkStatsService.

(cherry-picked from commit 5a7dea1a)

Bug: 29337859
Bug: 32163131
Test: builds, boots
Test: stats appear in tethering logs
Change-Id: If744f2e06cb6a3095a40199936b9afb76eff7b56
Merged-In: If744f2e06cb6a3095a40199936b9afb76eff7b56
parent 07f1304c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -211,7 +211,7 @@ public class Tethering extends BaseNetworkObserver {
        final Handler smHandler = mTetherMasterSM.getHandler();
        mOffloadController = new OffloadController(smHandler,
                deps.getOffloadHardwareInterface(smHandler, mLog),
                mContext.getContentResolver(),
                mContext.getContentResolver(), mNMService,
                mLog);
        mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
                mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
+87 −1
Original line number Diff line number Diff line
@@ -16,24 +16,36 @@

package com.android.server.connectivity.tethering;

import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;

import android.content.ContentResolver;
import android.net.ITetheringStatsProvider;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * A class to encapsulate the business logic of programming the tethering
@@ -44,6 +56,8 @@ import java.util.Set;
public class OffloadController {
    private static final String TAG = OffloadController.class.getSimpleName();

    private static final int STATS_FETCH_TIMEOUT_MS = 1000;

    private final Handler mHandler;
    private final OffloadHardwareInterface mHwInterface;
    private final ContentResolver mContentResolver;
@@ -59,14 +73,25 @@ public class OffloadController {
    // prefixes representing only the locally-assigned IP addresses.
    private Set<String> mLastLocalPrefixStrs;

    // Maps upstream interface names to offloaded traffic statistics.
    private HashMap<String, OffloadHardwareInterface.ForwardedStats>
            mForwardedStats = new HashMap<>();

    public OffloadController(Handler h, OffloadHardwareInterface hwi,
            ContentResolver contentResolver, SharedLog log) {
            ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
        mHandler = h;
        mHwInterface = hwi;
        mContentResolver = contentResolver;
        mLog = log.forSubComponent(TAG);
        mExemptPrefixes = new HashSet<>();
        mLastLocalPrefixStrs = new HashSet<>();

        try {
            nms.registerTetheringStatsProvider(
                    new OffloadTetheringStatsProvider(), getClass().getSimpleName());
        } catch (RemoteException e) {
            mLog.e("Cannot register offload stats provider: " + e);
        }
    }

    public void start() {
@@ -138,6 +163,7 @@ public class OffloadController {

    public void stop() {
        final boolean wasStarted = started();
        updateStatsForCurrentUpstream();
        mUpstreamLinkProperties = null;
        mHwInterface.stopOffloadControl();
        mControlInitialized = false;
@@ -145,16 +171,76 @@ public class OffloadController {
        if (wasStarted) mLog.log("tethering offload stopped");
    }

    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
        @Override
        public NetworkStats getTetherStats() {
            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
            CountDownLatch latch = new CountDownLatch(1);

            mHandler.post(() -> {
                try {
                    NetworkStats.Entry entry = new NetworkStats.Entry();
                    entry.set = SET_DEFAULT;
                    entry.tag = TAG_NONE;
                    entry.uid = UID_TETHERING;

                    updateStatsForCurrentUpstream();

                    for (String iface : mForwardedStats.keySet()) {
                        entry.iface = iface;
                        entry.rxBytes = mForwardedStats.get(iface).rxBytes;
                        entry.txBytes = mForwardedStats.get(iface).txBytes;
                        stats.addValues(entry);
                    }
                } finally {
                    latch.countDown();
                }
            });

            try {
                latch.await(STATS_FETCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                mLog.e("Tethering stats fetch timed out after " + STATS_FETCH_TIMEOUT_MS + "ms");
            }

            return stats;
        }
    }

    private void maybeUpdateStats(String iface) {
        if (TextUtils.isEmpty(iface)) {
            return;
        }

        if (!mForwardedStats.containsKey(iface)) {
            mForwardedStats.put(iface, new OffloadHardwareInterface.ForwardedStats());
        }
        mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface));
    }

    private void updateStatsForCurrentUpstream() {
        if (mUpstreamLinkProperties != null) {
            maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
        }
    }

    public void setUpstreamLinkProperties(LinkProperties lp) {
        if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;

        String prevUpstream = (mUpstreamLinkProperties != null) ?
                mUpstreamLinkProperties.getInterfaceName() : null;

        mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;

        // TODO: examine return code and decide what to do if programming
        // upstream parameters fails (probably just wait for a subsequent
        // onOffloadEvent() callback to tell us offload is available again and
        // then reapply all state).
        computeAndPushLocalPrefixes();
        pushUpstreamParameters();

        // Update stats after we've told the hardware to change routing so we don't miss packets.
        maybeUpdateStats(prevUpstream);
    }

    public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
+1 −0
Original line number Diff line number Diff line
@@ -178,6 +178,7 @@ public class TetheringTest {
        mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
                                   mLooper.getLooper(), mSystemProperties,
                                   mTetheringDependencies);
        verify(mNMService).registerTetheringStatsProvider(any(), anyString());
    }

    @After
+101 −15
Original line number Diff line number Diff line
@@ -16,26 +16,38 @@

package com.android.server.connectivity.tethering;

import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.ITetheringStatsProvider;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Looper;
import android.os.INetworkManagementService;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;

@@ -66,11 +78,14 @@ public class OffloadControllerTest {
    @Mock private OffloadHardwareInterface mHardware;
    @Mock private ApplicationInfo mApplicationInfo;
    @Mock private Context mContext;
    @Mock private INetworkManagementService mNMService;
    private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
            ArgumentCaptor.forClass(ArrayList.class);
    private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
            ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
    private MockContentResolver mContentResolver;

    @Before public void setUp() throws Exception {
    @Before public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
        when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
@@ -90,15 +105,24 @@ public class OffloadControllerTest {
        when(mHardware.initOffloadConfig()).thenReturn(true);
        when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
                .thenReturn(true);
        when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
    }

    private void enableOffload() {
        Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
    }

    private OffloadController makeOffloadController() throws Exception {
        OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
                mHardware, mContentResolver, mNMService, new SharedLog("test"));
        verify(mNMService).registerTetheringStatsProvider(
                mTetherStatsProviderCaptor.capture(), anyString());
        return offload;
    }

    // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available.
    // @Test
    public void testNoSettingsValueDefaultDisabledDoesNotStart() {
    public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception {
        setupFunctioningHardwareInterface();
        when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
        try {
@@ -106,8 +130,7 @@ public class OffloadControllerTest {
            fail();
        } catch (SettingNotFoundException expected) {}

        final OffloadController offload =
                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
        final OffloadController offload = makeOffloadController();
        offload.start();

        final InOrder inOrder = inOrder(mHardware);
@@ -120,7 +143,7 @@ public class OffloadControllerTest {

    // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available.
    // @Test
    public void testNoSettingsValueDefaultEnabledDoesStart() {
    public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception {
        setupFunctioningHardwareInterface();
        when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
        try {
@@ -128,8 +151,7 @@ public class OffloadControllerTest {
            fail();
        } catch (SettingNotFoundException expected) {}

        final OffloadController offload =
                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
        final OffloadController offload = makeOffloadController();
        offload.start();

        final InOrder inOrder = inOrder(mHardware);
@@ -141,12 +163,11 @@ public class OffloadControllerTest {
    }

    @Test
    public void testSettingsAllowsStart() {
    public void testSettingsAllowsStart() throws Exception {
        setupFunctioningHardwareInterface();
        Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);

        final OffloadController offload =
                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
        final OffloadController offload = makeOffloadController();
        offload.start();

        final InOrder inOrder = inOrder(mHardware);
@@ -158,12 +179,11 @@ public class OffloadControllerTest {
    }

    @Test
    public void testSettingsDisablesStart() {
    public void testSettingsDisablesStart() throws Exception {
        setupFunctioningHardwareInterface();
        Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);

        final OffloadController offload =
                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
        final OffloadController offload = makeOffloadController();
        offload.start();

        final InOrder inOrder = inOrder(mHardware);
@@ -178,8 +198,7 @@ public class OffloadControllerTest {
        setupFunctioningHardwareInterface();
        enableOffload();

        final OffloadController offload =
                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
        final OffloadController offload = makeOffloadController();
        offload.start();

        final InOrder inOrder = inOrder(mHardware);
@@ -244,6 +263,7 @@ public class OffloadControllerTest {
        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                eq(testIfName), eq(ipv4Addr), eq(null), eq(null));
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
        inOrder.verifyNoMoreInteractions();

        final String ipv4Gateway = "192.0.2.1";
@@ -253,6 +273,7 @@ public class OffloadControllerTest {
        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null));
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
        inOrder.verifyNoMoreInteractions();

        final String ipv6Gw1 = "fe80::cafe";
@@ -262,6 +283,7 @@ public class OffloadControllerTest {
        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
        ArrayList<String> v6gws = mStringArrayCaptor.getValue();
        assertEquals(1, v6gws.size());
        assertTrue(v6gws.contains(ipv6Gw1));
@@ -274,6 +296,7 @@ public class OffloadControllerTest {
        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
        v6gws = mStringArrayCaptor.getValue();
        assertEquals(2, v6gws.size());
        assertTrue(v6gws.contains(ipv6Gw1));
@@ -291,6 +314,7 @@ public class OffloadControllerTest {
        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
        v6gws = mStringArrayCaptor.getValue();
        assertEquals(2, v6gws.size());
        assertTrue(v6gws.contains(ipv6Gw1));
@@ -325,6 +349,7 @@ public class OffloadControllerTest {
        assertEquals(2, v6gws.size());
        assertTrue(v6gws.contains(ipv6Gw1));
        assertTrue(v6gws.contains(ipv6Gw2));
        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
        inOrder.verifyNoMoreInteractions();

        // Completely identical LinkProperties updates are de-duped.
@@ -335,4 +360,65 @@ public class OffloadControllerTest {
                anyObject(), anyObject(), anyObject(), anyObject());
        inOrder.verifyNoMoreInteractions();
    }

    private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
        assertEquals(iface, entry.iface);
        assertEquals(stats.rxBytes, entry.rxBytes);
        assertEquals(stats.txBytes, entry.txBytes);
        assertEquals(SET_DEFAULT, entry.set);
        assertEquals(TAG_NONE, entry.tag);
        assertEquals(UID_TETHERING, entry.uid);
    }

    @Test
    public void testGetForwardedStats() throws Exception {
        setupFunctioningHardwareInterface();
        enableOffload();

        final OffloadController offload = makeOffloadController();
        offload.start();

        final String ethernetIface = "eth1";
        final String mobileIface = "rmnet_data0";

        ForwardedStats ethernetStats = new ForwardedStats();
        ethernetStats.rxBytes = 12345;
        ethernetStats.txBytes = 54321;

        ForwardedStats mobileStats = new ForwardedStats();
        mobileStats.rxBytes = 999;
        mobileStats.txBytes = 99999;

        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);

        final LinkProperties lp = new LinkProperties();
        lp.setInterfaceName(ethernetIface);
        offload.setUpstreamLinkProperties(lp);

        lp.setInterfaceName(mobileIface);
        offload.setUpstreamLinkProperties(lp);

        lp.setInterfaceName(ethernetIface);
        offload.setUpstreamLinkProperties(lp);

        ethernetStats.rxBytes = 100000;
        ethernetStats.txBytes = 100000;
        offload.setUpstreamLinkProperties(null);

        NetworkStats stats = mTetherStatsProviderCaptor.getValue().getTetherStats();
        assertEquals(2, stats.size());

        NetworkStats.Entry entry = null;
        int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
        int mobilePosition = 1 - ethernetPosition;

        entry = stats.getValues(mobilePosition, entry);
        assertNetworkStats(mobileIface, mobileStats, entry);

        ethernetStats.rxBytes = 12345 + 100000;
        ethernetStats.txBytes = 54321 + 100000;
        entry = stats.getValues(ethernetPosition, entry);
        assertNetworkStats(ethernetIface, ethernetStats, entry);
    }
}