From 97482de1fd3824be733c11a9e0a2c1ad0a2eb702 Mon Sep 17 00:00:00 2001 From: Varun Anand Date: Sun, 17 Feb 2019 23:43:25 -0800 Subject: [PATCH 1/4] Take all VPN underlying networks into account when migrating traffic for VPN uid. (cherry picked from commit 612520f5448b6d46c05ca60fb99c8d45433a9474) Bug: 113122541 Bug: 120145746 Test: atest FrameworksNetTests Test: Manually verified on device that stats from VPN UID are moved appropriately based on its declared underlying network set. Test: vogar --mode app_process --benchmark NetworkStatsBenchmark.java Change-Id: I7f368c5970b2dcb969fe0daf5ef44edb1f51d09d --- .../android/server/ConnectivityService.java | 27 ++-- .../java/android/net/NetworkStatsTest.java | 4 +- .../server/net/NetworkStatsServiceTest.java | 142 +++++++++++++++++- 3 files changed, 154 insertions(+), 19 deletions(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index f12bfc33ea..c20f01fcf5 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -4366,7 +4366,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * @return VPN information for accounting, or null if we can't retrieve all required - * information, e.g primary underlying iface. + * information, e.g underlying ifaces. */ @Nullable private VpnInfo createVpnInfo(Vpn vpn) { @@ -4378,17 +4378,24 @@ public class ConnectivityService extends IConnectivityManager.Stub // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret // the underlyingNetworks list. if (underlyingNetworks == null) { - NetworkAgentInfo defaultNetwork = getDefaultNetwork(); - if (defaultNetwork != null && defaultNetwork.linkProperties != null) { - info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName(); - } - } else if (underlyingNetworks.length > 0) { - LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]); - if (linkProperties != null) { - info.primaryUnderlyingIface = linkProperties.getInterfaceName(); + NetworkAgentInfo defaultNai = getDefaultNetwork(); + if (defaultNai != null && defaultNai.linkProperties != null) { + underlyingNetworks = new Network[] { defaultNai.network }; } } - return info.primaryUnderlyingIface == null ? null : info; + if (underlyingNetworks != null && underlyingNetworks.length > 0) { + List interfaces = new ArrayList<>(); + for (Network network : underlyingNetworks) { + LinkProperties lp = getLinkProperties(network); + if (lp != null) { + interfaces.add(lp.getInterfaceName()); + } + } + if (!interfaces.isEmpty()) { + info.underlyingIfaces = interfaces.toArray(new String[interfaces.size()]); + } + } + return info.underlyingIfaces == null ? null : info; } /** diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index b5b0384ca5..9ed6cb9b45 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -569,7 +569,7 @@ public class NetworkStatsTest { .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); - assertTrue(delta.toString(), delta.migrateTun(tunUid, tunIface, underlyingIface)); + delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface}); assertEquals(20, delta.size()); // tunIface and TEST_IFACE entries are not changed. @@ -650,7 +650,7 @@ public class NetworkStatsTest { .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L); - assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface)); + delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface}); assertEquals(9, delta.size()); // tunIface entries should not be changed. diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index bce526d3ae..c46534c8d0 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -927,7 +927,7 @@ public class NetworkStatsServiceTest { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). expectDefaultSettings(); NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()}; - VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; expectNetworkStatsUidDetail(buildEmptyStats()); expectBandwidthControlCheck(); @@ -947,8 +947,10 @@ public class NetworkStatsServiceTest { expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L) .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 500L, 50L, 500L, 50L, 1L) - .addValues( - TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 1650L, 150L, 2L)); + // VPN received 1650 bytes over WiFi in background (SET_DEFAULT). + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 0L, 0L, 1L) + // VPN sent 1650 bytes over WiFi in foreground (SET_FOREGROUND). + .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1650L, 150L, 1L)); forcePollAndWaitForIdle(); @@ -962,7 +964,7 @@ public class NetworkStatsServiceTest { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). expectDefaultSettings(); NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()}; - VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; expectNetworkStatsUidDetail(buildEmptyStats()); expectBandwidthControlCheck(); @@ -992,6 +994,132 @@ public class NetworkStatsServiceTest { assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0); } + @Test + public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is duplicating traffic across both WiFi and Cell. + expectDefaultSettings(); + NetworkState[] networkStates = + new NetworkState[] { + buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() + }; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN. + // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total). + // Of 8800 bytes over WiFi/Cell, expect: + // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE. + // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L) + .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L) + .addValues( + TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L)); + + forcePollAndWaitForIdle(); + + assertUidTotal(sTemplateWifi, UID_RED, 500L, 50L, 500L, 50L, 1); + assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 1200L, 100L, 1200L, 100L, 2); + + assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 500L, 50L, 500L, 50L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_BLUE, 500L, 50L, 500L, 50L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 1200L, 100L, 1200L, 100L, 2); + } + + @Test + public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell. + expectDefaultSettings(); + NetworkState[] networkStates = + new NetworkState[] { + buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() + }; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. + // VPN sent/received 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell. + // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for both + // rx/tx. + // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for both rx/tx. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 660L, 60L, 660L, 60L, 1L) + .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 440L, 40L, 440L, 40L, 1L)); + + forcePollAndWaitForIdle(); + + assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 60L, 0L, 60L, 0L, 1); + + assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 400L, 40L, 400L, 40L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 40L, 0L, 40L, 0L, 1); + } + + @Test + public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell. + expectDefaultSettings(); + NetworkState[] networkStates = + new NetworkState[] { + buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() + }; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface: + // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. + // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell. + // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both + // rx/tx. + // UID_VPN gets nothing attributed to it (avoiding negative stats). + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 600L, 60L, 600L, 60L, 0L) + .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 200L, 20L, 200L, 20L, 0L)); + + forcePollAndWaitForIdle(); + + assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 0); + assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0); + + assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 200L, 20L, 0); + assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 0L, 0L, 0L, 0L, 0); + } + @Test public void vpnWithIncorrectUnderlyingIface() throws Exception { // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2), @@ -1001,7 +1129,7 @@ public class NetworkStatsServiceTest { new NetworkState[] { buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState() }; - VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; expectNetworkStatsUidDetail(buildEmptyStats()); expectBandwidthControlCheck(); @@ -1382,11 +1510,11 @@ public class NetworkStatsServiceTest { return new NetworkState(info, prop, new NetworkCapabilities(), VPN_NETWORK, null, null); } - private static VpnInfo createVpnInfo(String underlyingIface) { + private static VpnInfo createVpnInfo(String[] underlyingIfaces) { VpnInfo info = new VpnInfo(); info.ownerUid = UID_VPN; info.vpnIface = TUN_IFACE; - info.primaryUnderlyingIface = underlyingIface; + info.underlyingIfaces = underlyingIfaces; return info; } From e7094673d53330ce501d3ccb946a91ded1ba7ccb Mon Sep 17 00:00:00 2001 From: Varun Anand Date: Fri, 18 Jan 2019 19:22:48 -0800 Subject: [PATCH 2/4] NetworkStatsService: Fix getDetailedUidStats to take VPNs into account. (cherry picked from commit 2af0b66aba89a4d1eeed69823967b659628f271c) This API is similar to one provided by NetworkStatsFactory with the difference that NSS also migrates traffic from VPN UID to other apps. Since traffic can only be migrated over NetworkStats delta, NSS therefore maintains NetworkStats snapshot across all UIDs/ifaces/tags. This snapshot gets updated whenever NSS records a new snapshot (based on various hooks such as VPN updating its underlying networks, network getting lost, etc.), or getDetailedUidStats API is invoked by one of its callers. Bug: 113122541 Bug: 120145746 Test: atest FrameworksNetTests Test: manually verified that battery stats are migrating traffic off of TUN (after patching above CL where we point BatteryStats to use this API). Change-Id: I4b8d7c5b6905a4a12c1806dfd35c2c4c63610404 --- .../java/android/net/NetworkStatsTest.java | 31 ++++ .../server/net/NetworkStatsServiceTest.java | 148 +++++++++++++++++- 2 files changed, 173 insertions(+), 6 deletions(-) diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index 9ed6cb9b45..c16a0f4466 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -812,6 +812,37 @@ public class NetworkStatsTest { assertEquals(entry2, stats.getValues(1, null)); } + @Test + public void testFilterDebugEntries() { + NetworkStats.Entry entry1 = new NetworkStats.Entry( + "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "test2", 10101, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry3 = new NetworkStats.Entry( + "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry4 = new NetworkStats.Entry( + "test2", 10101, SET_DBG_VPN_OUT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 4) + .addValues(entry1) + .addValues(entry2) + .addValues(entry3) + .addValues(entry4); + + stats.filterDebugEntries(); + + assertEquals(2, stats.size()); + assertEquals(entry1, stats.getValues(0, null)); + assertEquals(entry3, stats.getValues(1, null)); + } + @Test public void testApply464xlatAdjustments() { final String v4Iface = "v4-wlan0"; diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index c46534c8d0..e35c34affd 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -57,6 +57,7 @@ import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_PO import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -216,11 +217,16 @@ public class NetworkStatsServiceTest { expectNetworkStatsUidDetail(buildEmptyStats()); expectSystemReady(); + assertNull(mService.getTunAdjustedStats()); mService.systemReady(); + // Verify that system ready fetches realtime stats and initializes tun adjusted stats. + verify(mNetManager).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL); + assertNotNull("failed to initialize TUN adjusted stats", mService.getTunAdjustedStats()); + assertEquals(0, mService.getTunAdjustedStats().size()); + mSession = mService.openSession(); assertNotNull("openSession() failed", mSession); - // catch INetworkManagementEventObserver during systemReady() ArgumentCaptor networkObserver = ArgumentCaptor.forClass(INetworkManagementEventObserver.class); @@ -733,11 +739,13 @@ public class NetworkStatsServiceTest { NetworkStats stats = mService.getDetailedUidStats(ifaceFilter); - verify(mNetManager, times(1)).getNetworkStatsUidDetail(eq(UID_ALL), argThat(ifaces -> - ifaces != null && ifaces.length == 2 - && ArrayUtils.contains(ifaces, TEST_IFACE) - && ArrayUtils.contains(ifaces, stackedIface))); - + // mNetManager#getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL) has following invocations: + // 1) NetworkStatsService#systemReady from #setUp. + // 2) mService#forceUpdateIfaces in the test above. + // 3) Finally, mService#getDetailedUidStats. + verify(mNetManager, times(3)).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL); + assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE)); + assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface)); assertEquals(2, stats.size()); assertEquals(uidStats, stats.getValues(0, null)); assertEquals(tetheredStats1, stats.getValues(1, null)); @@ -1157,6 +1165,134 @@ public class NetworkStatsServiceTest { assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 1100L, 100L, 1100L, 100L, 1); } + @Test + public void recordSnapshot_migratesTunTrafficAndUpdatesTunAdjustedStats() throws Exception { + assertEquals(0, mService.getTunAdjustedStats().size()); + // VPN using WiFi (TEST_IFACE). + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectBandwidthControlCheck(); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were downloaded by UID_RED over VPN. + // VPN received 1100 bytes (100 packets) over WiFi. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L)); + + // this should lead to NSS#recordSnapshotLocked + mService.forceUpdateIfaces( + new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */); + + // Verify TUN adjusted stats have traffic migrated correctly. + // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100 + // bytes attributed to UID_VPN. + NetworkStats tunAdjStats = mService.getTunAdjustedStats(); + assertValues( + tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0); + } + + @Test + public void getDetailedUidStats_migratesTunTrafficAndUpdatesTunAdjustedStats() + throws Exception { + assertEquals(0, mService.getTunAdjustedStats().size()); + // VPN using WiFi (TEST_IFACE). + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectBandwidthControlCheck(); + mService.forceUpdateIfaces( + new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were downloaded by UID_RED over VPN. + // VPN received 1100 bytes (100 packets) over WiFi. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L)); + + mService.getDetailedUidStats(INTERFACES_ALL); + + // Verify internally maintained TUN adjusted stats + NetworkStats tunAdjStats = mService.getTunAdjustedStats(); + // Verify stats for TEST_IFACE (WiFi): + // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100 + // bytes attributed to UID_VPN. + assertValues( + tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0); + // Verify stats for TUN_IFACE; only UID_RED should have usage on it. + assertValues( + tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0); + + // lets assume that since last time, VPN received another 1100 bytes (same assumptions as + // before i.e. UID_RED downloaded another 1000 bytes). + incrementCurrentTime(HOUR_IN_MILLIS); + // Note - NetworkStatsFactory returns counters that are monotonically increasing. + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 0L, 0L, 0L)); + + mService.getDetailedUidStats(INTERFACES_ALL); + + tunAdjStats = mService.getTunAdjustedStats(); + // verify TEST_IFACE stats: + assertValues( + tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0); + assertValues( + tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 200L, 0L, 0L, 0L, 0); + // verify TUN_IFACE stats: + assertValues( + tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0); + assertValues( + tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0); + } + + @Test + public void getDetailedUidStats_returnsCorrectStatsWithVpnRunning() throws Exception { + // VPN using WiFi (TEST_IFACE). + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectBandwidthControlCheck(); + mService.forceUpdateIfaces( + new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were downloaded by UID_RED over VPN. + // VPN received 1100 bytes (100 packets) over WiFi. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L)); + + // Query realtime stats for TEST_IFACE. + NetworkStats queriedStats = + mService.getDetailedUidStats(new String[] {TEST_IFACE}); + + assertEquals(HOUR_IN_MILLIS, queriedStats.getElapsedRealtime()); + // verify that returned stats are only for TEST_IFACE and VPN traffic is migrated correctly. + assertEquals(new String[] {TEST_IFACE}, queriedStats.getUniqueIfaces()); + assertValues( + queriedStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0); + assertValues( + queriedStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0); + } + @Test public void testRegisterUsageCallback() throws Exception { // pretend that wifi network comes online; service should ask about full From 78d5ac4f8f5dfbd0b771f1ade8b28914074d5a2a Mon Sep 17 00:00:00 2001 From: Varun Anand Date: Fri, 24 May 2019 16:29:22 -0700 Subject: [PATCH 3/4] Addressing comments for http://ag/7700679. Note, that its in a separate CL so we could cherry-pick this CL to aosp. http://ag/7700679 is already in aosp. Bug: 113122541 Bug: 120145746 Test: atest FrameworksNetTests Change-Id: I7cfda226b4ed11b67002b83b38fba0f5caf96718 --- .../android/server/ConnectivityService.java | 4 +- .../server/net/NetworkStatsServiceTest.java | 115 ++++++++++++++---- 2 files changed, 93 insertions(+), 26 deletions(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c20f01fcf5..bf65c2949b 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -4379,7 +4379,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // the underlyingNetworks list. if (underlyingNetworks == null) { NetworkAgentInfo defaultNai = getDefaultNetwork(); - if (defaultNai != null && defaultNai.linkProperties != null) { + if (defaultNai != null) { underlyingNetworks = new Network[] { defaultNai.network }; } } @@ -4387,7 +4387,7 @@ public class ConnectivityService extends IConnectivityManager.Stub List interfaces = new ArrayList<>(); for (Network network : underlyingNetworks) { LinkProperties lp = getLinkProperties(network); - if (lp != null) { + if (lp != null && !TextUtils.isEmpty(lp.getInterfaceName())) { interfaces.add(lp.getInterfaceName()); } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index e35c34affd..66a267665e 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -930,6 +930,65 @@ public class NetworkStatsServiceTest { assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0); } + @Test + public void vpnRewriteTrafficThroughItself() throws Exception { + // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). + expectDefaultSettings(); + NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN. + // + // VPN UID rewrites packets read from TUN back to TUN, plus some of its own traffic + // (100 bytes). + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 5) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L) + .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L) + // VPN rewrites all the packets read from TUN + 100 additional bytes of VPN's + // own traffic. + .addValues(TUN_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 0L, 0L, 1600L, 160L, 2L) + // VPN sent 1760 bytes over WiFi in foreground (SET_FOREGROUND) i.e. 1600 + // bytes (160 packets) + 1 byte/packet overhead (=160 bytes). + .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1760L, 176L, 1L) + // VPN received 3300 bytes over WiFi in background (SET_DEFAULT) i.e. 3000 bytes + // (300 packets) + 1 byte/packet encryption overhead (=300 bytes). + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 3300L, 300L, 0L, 0L, 1L)); + + forcePollAndWaitForIdle(); + + // Verify increased TUN usage by UID_VPN does not get attributed to other apps. + NetworkStats tunStats = + mService.getDetailedUidStats(new String[] {TUN_IFACE}); + assertValues( + tunStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 2000L, 200L, 1000L, 100L, 1); + assertValues( + tunStats, TUN_IFACE, UID_BLUE, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 500L, 50L, 1); + assertValues( + tunStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 0L, 0L, 1600L, 160L, 2); + + // Verify correct attribution over WiFi. + assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1); + assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 300L, 0L, 260L, 26L, 2); + } + @Test public void vpnWithOneUnderlyingIface() throws Exception { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). @@ -946,25 +1005,29 @@ public class NetworkStatsServiceTest { getActiveIface(networkStates)); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): - // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. - // 500 bytes (50 packets) were sent/received by UID_BLUE over VPN. - // VPN sent/received 1650 bytes (150 packets) over WiFi. - // Of 1650 bytes over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes attributed to - // UID_BLUE, and 150 bytes attributed to UID_VPN for both rx/tx traffic. + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN. + // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over WiFi. + // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN. + // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes + // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN. incrementCurrentTime(HOUR_IN_MILLIS); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L) - .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 500L, 50L, 500L, 50L, 1L) - // VPN received 1650 bytes over WiFi in background (SET_DEFAULT). - .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 0L, 0L, 1L) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L) + .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L) + // VPN received 3300 bytes over WiFi in background (SET_DEFAULT). + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 3300L, 300L, 0L, 0L, 1L) // VPN sent 1650 bytes over WiFi in foreground (SET_FOREGROUND). .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1650L, 150L, 1L)); forcePollAndWaitForIdle(); - assertUidTotal(sTemplateWifi, UID_RED, 1000L, 100L, 1000L, 100L, 1); - assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1); - assertUidTotal(sTemplateWifi, UID_VPN, 150L, 0L, 150L, 0L, 2); + assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1); + assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 300L, 0L, 150L, 0L, 2); } @Test @@ -1068,24 +1131,28 @@ public class NetworkStatsServiceTest { getActiveIface(networkStates)); // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption // overhead per packet): - // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. - // VPN sent/received 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell. - // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for both - // rx/tx. - // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for both rx/tx. + // 1000 bytes (100 packets) were sent, and 500 bytes (50 packets) received by UID_RED over + // VPN. + // VPN sent 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell. + // And, it received 330 bytes (30 packets) over WiFi and 220 bytes (20 packets) over Cell. + // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for sent (tx) + // traffic. For received (rx) traffic, expect 300 bytes over WiFi and 200 bytes over Cell. + // + // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for tx traffic. + // And, 30 bytes over WiFi and 20 bytes over Cell for rx traffic. incrementCurrentTime(HOUR_IN_MILLIS); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L) - .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 660L, 60L, 660L, 60L, 1L) - .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 440L, 40L, 440L, 40L, 1L)); + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 500L, 50L, 1000L, 100L, 2L) + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 330L, 30L, 660L, 60L, 1L) + .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 220L, 20L, 440L, 40L, 1L)); forcePollAndWaitForIdle(); - assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 1); - assertUidTotal(sTemplateWifi, UID_VPN, 60L, 0L, 60L, 0L, 1); + assertUidTotal(sTemplateWifi, UID_RED, 300L, 30L, 600L, 60L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 30L, 0L, 60L, 0L, 1); - assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 400L, 40L, 400L, 40L, 1); - assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 40L, 0L, 40L, 0L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 400L, 40L, 1); + assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 20L, 0L, 40L, 0L, 1); } @Test From 20204cdf6a2a41408e036d87993241f66362a2d0 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Wed, 29 May 2019 19:02:41 +0900 Subject: [PATCH 4/4] Add one more test for VPN usage stats. Covers the case where the majority of traffic through the VPN is caused by the VPN app itself, and ensures that that traffic is correctly attributed to the VPN app as opposed to spread between the other apps that use the VPN. Bug: 120145746 Test: atest NetworkStatsServiceTest Change-Id: Iffd3f95fc2e11d311691a797b010edb38d2ef3c6 --- .../server/net/NetworkStatsServiceTest.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 66a267665e..d9f2c201fe 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -62,7 +62,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -1030,6 +1029,51 @@ public class NetworkStatsServiceTest { assertUidTotal(sTemplateWifi, UID_VPN, 300L, 0L, 150L, 0L, 2); } + @Test + public void vpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception { + // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). + expectDefaultSettings(); + NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + expectNetworkStatsUidDetail(buildEmptyStats()); + expectBandwidthControlCheck(); + + mService.forceUpdateIfaces( + new Network[] {WIFI_NETWORK, VPN_NETWORK}, + vpnInfos, + networkStates, + getActiveIface(networkStates)); + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN. + // Additionally, the VPN sends 6000 bytes (600 packets) of its own traffic into the tun + // interface (passing that traffic to the VPN endpoint), and receives 5000 bytes (500 + // packets) from it. Including overhead that is 6600/5500 bytes. + // VPN sent 8250 bytes (750 packets), and received 8800 (800 packets) over WiFi. + // Of 8250 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 6750 bytes attributed to UID_VPN. + // Of 8800 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes + // attributed to UID_BLUE, and 5800 bytes attributed to UID_VPN. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) + .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L) + .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L) + .addValues(TUN_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 5000L, 500L, 6000L, 600L, 1L) + // VPN received 8800 bytes over WiFi in background (SET_DEFAULT). + .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 8800L, 800L, 0L, 0L, 1L) + // VPN sent 8250 bytes over WiFi in foreground (SET_FOREGROUND). + .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 8250L, 750L, 1L)); + + forcePollAndWaitForIdle(); + + assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1); + assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1); + assertUidTotal(sTemplateWifi, UID_VPN, 5800L, 500L, 6750L, 600L, 2); + } + @Test public void vpnWithOneUnderlyingIface_withCompression() throws Exception { // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).