Merge changes from topic "Intent spam" into main

* changes:
  Unit test for intent spam
  Fix spamming of network stats updated intent
This commit is contained in:
Yun-Chen Cheng 2024-08-12 05:14:16 +00:00 • committed by Gerrit Code Review
commit 925747a773
2 changed files with 185 additions and 5 deletions

View file

@ -74,12 +74,12 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIV
import static com.android.net.module.util.DeviceConfigUtils.getDeviceConfigPropertyInt;
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_DUMPSYS;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_FORCE_UPDATE;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_GLOBAL_ALERT;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_NETWORK_STATUS_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_OPEN_SESSION;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REG_CALLBACK;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_REMOVE_UIDS;
@ -242,13 +242,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
// A message for broadcasting ACTION_NETWORK_STATS_UPDATED in handler thread to prevent
// deadlock.
private static final int MSG_BROADCAST_NETWORK_STATS_UPDATED = 4;
/** Flags to control detail level of poll event. */
private static final int FLAG_PERSIST_NETWORK = 0x1;
private static final int FLAG_PERSIST_UID = 0x2;
private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
private static final int FLAG_PERSIST_FORCE = 0x100;
/**
* When global alert quota is high, wait for this delay before processing each polling,
* and do not schedule further polls once there is already one queued.
@ -313,6 +311,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
static final String TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME = "trafficstats_cache_max_entries";
static final int DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS = 1000;
static final int DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES = 400;
/**
* The delay time between to network stats update intents.
* Added to fix intent spams (b/3115462)
*/
@VisibleForTesting(visibility = PRIVATE)
static final int BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS = 1000;
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@ -385,6 +389,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
long getXtPersistBytes(long def);
long getUidPersistBytes(long def);
long getUidTagPersistBytes(long def);
long getBroadcastNetworkStatsUpdateDelayMs();
}
private final Object mStatsLock = new Object();
@ -469,15 +474,36 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private long mLastStatsSessionPoll;
/**
* The timestamp of the most recent network stats broadcast.
*
* Note that this time could be in the past for completed broadcasts,
* or in the future for scheduled broadcasts.
*
* It is initialized to {@code Long.MIN_VALUE} to ensure that the first broadcast request
* is fulfilled immediately, regardless of the delay time.
*
* This value is used to enforce rate limiting on intents, preventing intent spam.
*/
@GuardedBy("mStatsLock")
private long mLatestNetworkStatsUpdatedBroadcastScheduledTime = Long.MIN_VALUE;
private final TrafficStatsRateLimitCache mTrafficStatsTotalCache;
private final TrafficStatsRateLimitCache mTrafficStatsIfaceCache;
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
static final String TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
static final String BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG =
"broadcast_network_stats_updated_rate_limit_enabled_flag";
private final boolean mAlwaysUseTrafficStatsRateLimitCache;
private final int mTrafficStatsRateLimitCacheExpiryDuration;
private final int mTrafficStatsRateLimitCacheMaxEntries;
private final boolean mBroadcastNetworkStatsUpdatedRateLimitEnabled;
private final Object mOpenSessionCallsLock = new Object();
/**
@ -669,6 +695,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
mAlwaysUseTrafficStatsRateLimitCache =
mDeps.alwaysUseTrafficStatsRateLimitCache(mContext);
mBroadcastNetworkStatsUpdatedRateLimitEnabled =
mDeps.enabledBroadcastNetworkStatsUpdatedRateLimiting(mContext);
mTrafficStatsRateLimitCacheExpiryDuration =
mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
mTrafficStatsRateLimitCacheMaxEntries =
@ -695,6 +723,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
// TODO: Move more stuff into dependencies object.
@VisibleForTesting
public static class Dependencies {
/**
* Get broadcast network stats updated delay time in ms
* @return
*/
@NonNull
public long getBroadcastNetworkStatsUpdateDelayMs() {
return BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
}
/**
* Get legacy platform stats directory.
*/
@ -926,6 +963,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
ctx, CONFIG_ENABLE_NETWORK_STATS_EVENT_LOGGER);
}
/**
* Get whether broadcast network stats update rate limiting is enabled.
*
* This method should only be called once in the constructor,
* to ensure that the code does not need to deal with flag values changing at runtime.
*/
public boolean enabledBroadcastNetworkStatsUpdatedRateLimiting(Context ctx) {
return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
ctx, BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG);
}
/**
* Get whether TrafficStats rate-limit cache is always applied.
*
@ -2645,8 +2693,22 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
performSampleLocked();
}
// finally, dispatch updated event to any listeners
mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
// Dispatch updated event to listeners, preventing intent spamming
// (b/343844995) possibly from abnormal modem RAT changes or misbehaving
// app calls (see NetworkStatsEventLogger#POLL_REASON_* for possible reasons).
// If no broadcasts are scheduled, use the time of the last broadcast
// to schedule the next one ASAP.
if (!mBroadcastNetworkStatsUpdatedRateLimitEnabled) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED));
} else if (mLatestNetworkStatsUpdatedBroadcastScheduledTime < SystemClock.uptimeMillis()) {
mLatestNetworkStatsUpdatedBroadcastScheduledTime = Math.max(
mLatestNetworkStatsUpdatedBroadcastScheduledTime
+ mSettings.getBroadcastNetworkStatsUpdateDelayMs(),
SystemClock.uptimeMillis()
);
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_BROADCAST_NETWORK_STATS_UPDATED),
mLatestNetworkStatsUpdatedBroadcastScheduledTime);
}
Trace.traceEnd(TRACE_TAG_NETWORK);
}
@ -3605,6 +3667,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
public long getUidTagPersistBytes(long def) {
return def;
}
@Override
public long getBroadcastNetworkStatsUpdateDelayMs() {
return BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
}
}
// TODO: Read stats by using BpfNetMapsReader.

View file

@ -69,6 +69,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.PollEvent.pollReasonNameOf;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static com.android.server.net.NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
import static com.android.server.net.NetworkStatsService.NETSTATS_FASTDATAINPUT_FALLBACKS_COUNTER_NAME;
@ -82,6 +84,7 @@ import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalMatchers.aryEq;
@ -101,8 +104,10 @@ import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@ -138,6 +143,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
@ -150,6 +156,7 @@ import androidx.test.filters.SmallTest;
import com.android.connectivity.resources.R;
import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
@ -617,6 +624,12 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
return mFeatureFlags.getOrDefault(TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, false);
}
@Override
public boolean enabledBroadcastNetworkStatsUpdatedRateLimiting(Context ctx) {
return mFeatureFlags.getOrDefault(
BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG, true);
}
@Override
public int getTrafficStatsRateLimitCacheExpiryDuration() {
return DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
@ -2617,6 +2630,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
private void mockDefaultSettings() throws Exception {
mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
mSettings.setBroadcastNetworkStatsUpdateDelayMs(
NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS);
}
private void mockSettings(long bucketDuration, long deleteAge) {
@ -2631,6 +2646,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
@NonNull
private volatile Config mConfig;
private final AtomicBoolean mCombineSubtypeEnabled = new AtomicBoolean();
private long mBroadcastNetworkStatsUpdateDelayMs =
NetworkStatsService.BROADCAST_NETWORK_STATS_UPDATED_DELAY_MS;
TestNetworkStatsSettings(long bucketDuration, long deleteAge) {
mConfig = new Config(bucketDuration, deleteAge, deleteAge);
@ -2693,6 +2710,15 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
public boolean getAugmentEnabled() {
return false;
}
@Override
public long getBroadcastNetworkStatsUpdateDelayMs() {
return mBroadcastNetworkStatsUpdateDelayMs;
}
public void setBroadcastNetworkStatsUpdateDelayMs(long broadcastDelay) {
mBroadcastNetworkStatsUpdateDelayMs = broadcastDelay;
}
}
private void assertStatsFilesExist(boolean exist) {
@ -3064,4 +3090,91 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
final String dump = getDump();
assertDumpContains(dump, "Log for testing");
}
private static class TestNetworkStatsUpdatedReceiver extends BroadcastReceiver {
private final ArrayTrackRecord<Intent>.ReadHead mHistory;
TestNetworkStatsUpdatedReceiver() {
mHistory = (new ArrayTrackRecord<Intent>()).newReadHead();
}
@Override
public void onReceive(Context context, Intent intent) {
mHistory.add(intent);
}
/**
* Assert no broadcast intent is received in blocking manner
*/
public void assertNoBroadcastIntentReceived() {
assertNull(mHistory.peek());
}
/**
* Assert an intent is received and remove it from queue
*/
public void assertBroadcastIntentReceived() {
assertNotNull(mHistory.poll(WAIT_TIMEOUT, number -> true));
}
}
@FeatureFlag(name = BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG)
@Test
public void testNetworkStatsUpdatedIntentSpam_rateLimitOn() throws Exception {
// Set the update delay long enough that messages won't be processed before unblocked
// Set a short time to test the behavior before reaching delay.
// Constraint: test running time < toleranceMs < update delay time
mSettings.setBroadcastNetworkStatsUpdateDelayMs(100_000L);
final long toleranceMs = 5000;
final TestableLooper mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
final TestNetworkStatsUpdatedReceiver receiver = new TestNetworkStatsUpdatedReceiver();
mServiceContext.registerReceiver(receiver, new IntentFilter(ACTION_NETWORK_STATS_UPDATED));
try {
// Test that before anything, the intent is delivered immediately
mService.forceUpdate();
mTestableLooper.processAllMessages();
receiver.assertBroadcastIntentReceived();
receiver.assertNoBroadcastIntentReceived();
// Test that the next two intents results in exactly one intent delivered
for (int i = 0; i < 2; i++) {
mService.forceUpdate();
}
// Test that the delay depends on our set value
mTestableLooper.moveTimeForward(mSettings.getBroadcastNetworkStatsUpdateDelayMs()
- toleranceMs);
mTestableLooper.processAllMessages();
receiver.assertNoBroadcastIntentReceived();
// Unblock messages and test that the second and third update
// is broadcasted right after the delay
mTestableLooper.moveTimeForward(toleranceMs);
mTestableLooper.processAllMessages();
receiver.assertBroadcastIntentReceived();
receiver.assertNoBroadcastIntentReceived();
} finally {
mTestableLooper.destroy();
}
}
@FeatureFlag(name = BROADCAST_NETWORK_STATS_UPDATED_RATE_LIMIT_ENABLED_FLAG, enabled = false)
@Test
public void testNetworkStatsUpdatedIntentSpam_rateLimitOff() throws Exception {
// Set the update delay long enough to ensure that messages are processed
// despite the rate limit.
mSettings.setBroadcastNetworkStatsUpdateDelayMs(100_000L);
final TestNetworkStatsUpdatedReceiver receiver = new TestNetworkStatsUpdatedReceiver();
mServiceContext.registerReceiver(receiver, new IntentFilter(ACTION_NETWORK_STATS_UPDATED));
for (int i = 0; i < 2; i++) {
mService.forceUpdate();
waitForIdle();
receiver.assertBroadcastIntentReceived();
}
receiver.assertNoBroadcastIntentReceived();
}
}