Move net unit tests to packages/Connectivity
Move the tests together with packages/Connectivity code, so both can be moved to packages/modules/Connectivity together. Also reorganize unit tests in a unit/ directory, as other tests (integration/, common/ etc.) have been added in tests/net since they were created. This makes the directory structure consistent. Test: atest FrameworksNetTests Bug: 187814163 Merged-In: I254ffd1c08ec058d594b4ea55cbae5505f8497cc Change-Id: I254ffd1c08ec058d594b4ea55cbae5505f8497cc
This commit is contained in:
12461
tests/unit/java/com/android/server/ConnectivityServiceTest.java
Normal file
12461
tests/unit/java/com/android/server/ConnectivityServiceTest.java
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.server;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.IpSecService.IResource;
|
||||
import com.android.server.IpSecService.RefcountedResource;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/** Unit tests for {@link IpSecService.RefcountedResource}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IpSecServiceRefcountedResourceTest {
|
||||
Context mMockContext;
|
||||
IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
|
||||
IpSecService mIpSecService;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mMockContext = mock(Context.class);
|
||||
mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
|
||||
mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
|
||||
}
|
||||
|
||||
private void assertResourceState(
|
||||
RefcountedResource<IResource> resource,
|
||||
int refCount,
|
||||
int userReleaseCallCount,
|
||||
int releaseReferenceCallCount,
|
||||
int invalidateCallCount,
|
||||
int freeUnderlyingResourcesCallCount)
|
||||
throws RemoteException {
|
||||
// Check refcount on RefcountedResource
|
||||
assertEquals(refCount, resource.mRefCount);
|
||||
|
||||
// Check call count of RefcountedResource
|
||||
verify(resource, times(userReleaseCallCount)).userRelease();
|
||||
verify(resource, times(releaseReferenceCallCount)).releaseReference();
|
||||
|
||||
// Check call count of IResource
|
||||
verify(resource.getResource(), times(invalidateCallCount)).invalidate();
|
||||
verify(resource.getResource(), times(freeUnderlyingResourcesCallCount))
|
||||
.freeUnderlyingResources();
|
||||
}
|
||||
|
||||
/** Adds mockito instrumentation */
|
||||
private RefcountedResource<IResource> getTestRefcountedResource(
|
||||
RefcountedResource... children) {
|
||||
return getTestRefcountedResource(new Binder(), children);
|
||||
}
|
||||
|
||||
/** Adds mockito instrumentation with provided binder */
|
||||
private RefcountedResource<IResource> getTestRefcountedResource(
|
||||
IBinder binder, RefcountedResource... children) {
|
||||
return spy(
|
||||
mIpSecService
|
||||
.new RefcountedResource<IResource>(mock(IResource.class), binder, children));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor() throws RemoteException {
|
||||
IBinder binderMock = mock(IBinder.class);
|
||||
RefcountedResource<IResource> resource = getTestRefcountedResource(binderMock);
|
||||
|
||||
// Verify resource's refcount starts at 1 (for user-reference)
|
||||
assertResourceState(resource, 1, 0, 0, 0, 0);
|
||||
|
||||
// Verify linking to binder death
|
||||
verify(binderMock).linkToDeath(anyObject(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorWithChildren() throws RemoteException {
|
||||
IBinder binderMockChild = mock(IBinder.class);
|
||||
IBinder binderMockParent = mock(IBinder.class);
|
||||
RefcountedResource<IResource> childResource = getTestRefcountedResource(binderMockChild);
|
||||
RefcountedResource<IResource> parentResource =
|
||||
getTestRefcountedResource(binderMockParent, childResource);
|
||||
|
||||
// Verify parent's refcount starts at 1 (for user-reference)
|
||||
assertResourceState(parentResource, 1, 0, 0, 0, 0);
|
||||
|
||||
// Verify child's refcounts were incremented
|
||||
assertResourceState(childResource, 2, 0, 0, 0, 0);
|
||||
|
||||
// Verify linking to binder death
|
||||
verify(binderMockChild).linkToDeath(anyObject(), anyInt());
|
||||
verify(binderMockParent).linkToDeath(anyObject(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailLinkToDeath() throws RemoteException {
|
||||
IBinder binderMock = mock(IBinder.class);
|
||||
doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
|
||||
|
||||
try {
|
||||
getTestRefcountedResource(binderMock);
|
||||
fail("Expected exception to propogate when binder fails to link to death");
|
||||
} catch (RuntimeException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupAndRelease() throws RemoteException {
|
||||
IBinder binderMock = mock(IBinder.class);
|
||||
RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
|
||||
|
||||
// Verify user-initiated cleanup path decrements refcount and calls full cleanup flow
|
||||
refcountedResource.userRelease();
|
||||
assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
|
||||
|
||||
// Verify user-initated cleanup path unlinks from binder
|
||||
verify(binderMock).unlinkToDeath(eq(refcountedResource), eq(0));
|
||||
assertNull(refcountedResource.mBinder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleCallsToCleanupAndRelease() throws RemoteException {
|
||||
RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
|
||||
|
||||
// Verify calling userRelease multiple times does not trigger any other cleanup
|
||||
// methods
|
||||
refcountedResource.userRelease();
|
||||
assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
|
||||
|
||||
refcountedResource.userRelease();
|
||||
refcountedResource.userRelease();
|
||||
assertResourceState(refcountedResource, -1, 3, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinderDeathAfterCleanupAndReleaseDoesNothing() throws RemoteException {
|
||||
RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
|
||||
|
||||
refcountedResource.userRelease();
|
||||
assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
|
||||
|
||||
// Verify binder death call does not trigger any other cleanup methods if called after
|
||||
// userRelease()
|
||||
refcountedResource.binderDied();
|
||||
assertResourceState(refcountedResource, -1, 2, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinderDeath() throws RemoteException {
|
||||
RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
|
||||
|
||||
// Verify binder death caused cleanup
|
||||
refcountedResource.binderDied();
|
||||
verify(refcountedResource, times(1)).binderDied();
|
||||
assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
|
||||
assertNull(refcountedResource.mBinder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupParentDecrementsChildRefcount() throws RemoteException {
|
||||
RefcountedResource<IResource> childResource = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
|
||||
|
||||
parentResource.userRelease();
|
||||
|
||||
// Verify parent gets cleaned up properly, and triggers releaseReference on
|
||||
// child
|
||||
assertResourceState(childResource, 1, 0, 1, 0, 0);
|
||||
assertResourceState(parentResource, -1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupReferencedChildDoesNotTriggerRelease() throws RemoteException {
|
||||
RefcountedResource<IResource> childResource = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
|
||||
|
||||
childResource.userRelease();
|
||||
|
||||
// Verify that child does not clean up kernel resources and quota.
|
||||
assertResourceState(childResource, 1, 1, 1, 1, 0);
|
||||
assertResourceState(parentResource, 1, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoParents() throws RemoteException {
|
||||
RefcountedResource<IResource> childResource = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> parentResource1 = getTestRefcountedResource(childResource);
|
||||
RefcountedResource<IResource> parentResource2 = getTestRefcountedResource(childResource);
|
||||
|
||||
// Verify that child does not cleanup kernel resources and quota until all references
|
||||
// have been released. Assumption: parents release correctly based on
|
||||
// testCleanupParentDecrementsChildRefcount()
|
||||
childResource.userRelease();
|
||||
assertResourceState(childResource, 2, 1, 1, 1, 0);
|
||||
|
||||
parentResource1.userRelease();
|
||||
assertResourceState(childResource, 1, 1, 2, 1, 0);
|
||||
|
||||
parentResource2.userRelease();
|
||||
assertResourceState(childResource, -1, 1, 3, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoChildren() throws RemoteException {
|
||||
RefcountedResource<IResource> childResource1 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> childResource2 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> parentResource =
|
||||
getTestRefcountedResource(childResource1, childResource2);
|
||||
|
||||
childResource1.userRelease();
|
||||
assertResourceState(childResource1, 1, 1, 1, 1, 0);
|
||||
assertResourceState(childResource2, 2, 0, 0, 0, 0);
|
||||
|
||||
parentResource.userRelease();
|
||||
assertResourceState(childResource1, -1, 1, 2, 1, 1);
|
||||
assertResourceState(childResource2, 1, 0, 1, 0, 0);
|
||||
|
||||
childResource2.userRelease();
|
||||
assertResourceState(childResource1, -1, 1, 2, 1, 1);
|
||||
assertResourceState(childResource2, -1, 1, 2, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSampleUdpEncapTranform() throws RemoteException {
|
||||
RefcountedResource<IResource> spi1 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> spi2 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> transform =
|
||||
getTestRefcountedResource(spi1, spi2, udpEncapSocket);
|
||||
|
||||
// Pretend one SPI goes out of reference (releaseManagedResource -> userRelease)
|
||||
spi1.userRelease();
|
||||
|
||||
// User called releaseManagedResource on udpEncap socket
|
||||
udpEncapSocket.userRelease();
|
||||
|
||||
// User dies, and binder kills the rest
|
||||
spi2.binderDied();
|
||||
transform.binderDied();
|
||||
|
||||
// Check resource states
|
||||
assertResourceState(spi1, -1, 1, 2, 1, 1);
|
||||
assertResourceState(spi2, -1, 1, 2, 1, 1);
|
||||
assertResourceState(udpEncapSocket, -1, 1, 2, 1, 1);
|
||||
assertResourceState(transform, -1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSampleDualTransformEncapSocket() throws RemoteException {
|
||||
RefcountedResource<IResource> spi1 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> spi2 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> spi3 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> spi4 = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
|
||||
RefcountedResource<IResource> transform1 =
|
||||
getTestRefcountedResource(spi1, spi2, udpEncapSocket);
|
||||
RefcountedResource<IResource> transform2 =
|
||||
getTestRefcountedResource(spi3, spi4, udpEncapSocket);
|
||||
|
||||
// Pretend one SPIs goes out of reference (releaseManagedResource -> userRelease)
|
||||
spi1.userRelease();
|
||||
|
||||
// User called releaseManagedResource on udpEncap socket and spi4
|
||||
udpEncapSocket.userRelease();
|
||||
spi4.userRelease();
|
||||
|
||||
// User dies, and binder kills the rest
|
||||
spi2.binderDied();
|
||||
spi3.binderDied();
|
||||
transform2.binderDied();
|
||||
transform1.binderDied();
|
||||
|
||||
// Check resource states
|
||||
assertResourceState(spi1, -1, 1, 2, 1, 1);
|
||||
assertResourceState(spi2, -1, 1, 2, 1, 1);
|
||||
assertResourceState(spi3, -1, 1, 2, 1, 1);
|
||||
assertResourceState(spi4, -1, 1, 2, 1, 1);
|
||||
assertResourceState(udpEncapSocket, -1, 1, 3, 1, 1);
|
||||
assertResourceState(transform1, -1, 1, 1, 1, 1);
|
||||
assertResourceState(transform2, -1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fuzzTest() throws RemoteException {
|
||||
List<RefcountedResource<IResource>> resources = new ArrayList<>();
|
||||
|
||||
// Build a tree of resources
|
||||
for (int i = 0; i < 100; i++) {
|
||||
// Choose a random number of children from the existing list
|
||||
int numChildren = ThreadLocalRandom.current().nextInt(0, resources.size() + 1);
|
||||
|
||||
// Build a (random) list of children
|
||||
Set<RefcountedResource<IResource>> children = new HashSet<>();
|
||||
for (int j = 0; j < numChildren; j++) {
|
||||
int childIndex = ThreadLocalRandom.current().nextInt(0, resources.size());
|
||||
children.add(resources.get(childIndex));
|
||||
}
|
||||
|
||||
RefcountedResource<IResource> newRefcountedResource =
|
||||
getTestRefcountedResource(
|
||||
children.toArray(new RefcountedResource[children.size()]));
|
||||
resources.add(newRefcountedResource);
|
||||
}
|
||||
|
||||
// Cleanup all resources in a random order
|
||||
List<RefcountedResource<IResource>> clonedResources =
|
||||
new ArrayList<>(resources); // shallow copy
|
||||
while (!clonedResources.isEmpty()) {
|
||||
int index = ThreadLocalRandom.current().nextInt(0, clonedResources.size());
|
||||
RefcountedResource<IResource> refcountedResource = clonedResources.get(index);
|
||||
refcountedResource.userRelease();
|
||||
clonedResources.remove(index);
|
||||
}
|
||||
|
||||
// Verify all resources were cleaned up properly
|
||||
for (RefcountedResource<IResource> refcountedResource : resources) {
|
||||
assertEquals(-1, refcountedResource.mRefCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
670
tests/unit/java/com/android/server/IpSecServiceTest.java
Normal file
670
tests/unit/java/com/android/server/IpSecServiceTest.java
Normal file
@@ -0,0 +1,670 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.server;
|
||||
|
||||
import static android.system.OsConstants.AF_INET;
|
||||
import static android.system.OsConstants.EADDRINUSE;
|
||||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
import static android.system.OsConstants.SOCK_DGRAM;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.INetd;
|
||||
import android.net.IpSecAlgorithm;
|
||||
import android.net.IpSecConfig;
|
||||
import android.net.IpSecManager;
|
||||
import android.net.IpSecSpiResponse;
|
||||
import android.net.IpSecUdpEncapResponse;
|
||||
import android.os.Binder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.StructStat;
|
||||
import android.util.Range;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import dalvik.system.SocketTagger;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Unit tests for {@link IpSecService}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IpSecServiceTest {
|
||||
|
||||
private static final int DROID_SPI = 0xD1201D;
|
||||
private static final int MAX_NUM_ENCAP_SOCKETS = 100;
|
||||
private static final int MAX_NUM_SPIS = 100;
|
||||
private static final int TEST_UDP_ENCAP_INVALID_PORT = 100;
|
||||
private static final int TEST_UDP_ENCAP_PORT_OUT_RANGE = 100000;
|
||||
|
||||
private static final InetAddress INADDR_ANY;
|
||||
|
||||
private static final byte[] AEAD_KEY = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
0x73, 0x61, 0x6C, 0x74
|
||||
};
|
||||
private static final byte[] CRYPT_KEY = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
|
||||
};
|
||||
private static final byte[] AUTH_KEY = {
|
||||
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
|
||||
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
|
||||
};
|
||||
|
||||
private static final IpSecAlgorithm AUTH_ALGO =
|
||||
new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
|
||||
private static final IpSecAlgorithm CRYPT_ALGO =
|
||||
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
|
||||
private static final IpSecAlgorithm AEAD_ALGO =
|
||||
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
|
||||
|
||||
static {
|
||||
try {
|
||||
INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Context mMockContext;
|
||||
INetd mMockNetd;
|
||||
IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
|
||||
IpSecService mIpSecService;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mMockContext = mock(Context.class);
|
||||
mMockNetd = mock(INetd.class);
|
||||
mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
|
||||
mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
|
||||
|
||||
// Injecting mock netd
|
||||
when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpSecServiceCreate() throws InterruptedException {
|
||||
IpSecService ipSecSrv = IpSecService.create(mMockContext);
|
||||
assertNotNull(ipSecSrv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReleaseInvalidSecurityParameterIndex() throws Exception {
|
||||
try {
|
||||
mIpSecService.releaseSecurityParameterIndex(1);
|
||||
fail("IllegalArgumentException not thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/** This function finds an available port */
|
||||
int findUnusedPort() throws Exception {
|
||||
// Get an available port.
|
||||
ServerSocket s = new ServerSocket(0);
|
||||
int port = s.getLocalPort();
|
||||
s.close();
|
||||
return port;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenAndCloseUdpEncapsulationSocket() throws Exception {
|
||||
int localport = -1;
|
||||
IpSecUdpEncapResponse udpEncapResp = null;
|
||||
|
||||
for (int i = 0; i < IpSecService.MAX_PORT_BIND_ATTEMPTS; i++) {
|
||||
localport = findUnusedPort();
|
||||
|
||||
udpEncapResp = mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
|
||||
assertNotNull(udpEncapResp);
|
||||
if (udpEncapResp.status == IpSecManager.Status.OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Else retry to reduce possibility for port-bind failures.
|
||||
}
|
||||
|
||||
assertNotNull(udpEncapResp);
|
||||
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
|
||||
assertEquals(localport, udpEncapResp.port);
|
||||
|
||||
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
|
||||
udpEncapResp.fileDescriptor.close();
|
||||
|
||||
// Verify quota and RefcountedResource objects cleaned up
|
||||
IpSecService.UserRecord userRecord =
|
||||
mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
|
||||
assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent);
|
||||
try {
|
||||
userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId);
|
||||
fail("Expected IllegalArgumentException on attempt to access deleted resource");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUdpEncapsulationSocketBinderDeath() throws Exception {
|
||||
IpSecUdpEncapResponse udpEncapResp =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
|
||||
IpSecService.UserRecord userRecord =
|
||||
mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
|
||||
IpSecService.RefcountedResource refcountedRecord =
|
||||
userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
|
||||
udpEncapResp.resourceId);
|
||||
|
||||
refcountedRecord.binderDied();
|
||||
|
||||
// Verify quota and RefcountedResource objects cleaned up
|
||||
assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent);
|
||||
try {
|
||||
userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId);
|
||||
fail("Expected IllegalArgumentException on attempt to access deleted resource");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenUdpEncapsulationSocketAfterClose() throws Exception {
|
||||
IpSecUdpEncapResponse udpEncapResp =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(udpEncapResp);
|
||||
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
|
||||
int localport = udpEncapResp.port;
|
||||
|
||||
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
|
||||
udpEncapResp.fileDescriptor.close();
|
||||
|
||||
/** Check if localport is available. */
|
||||
FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
Os.bind(newSocket, INADDR_ANY, localport);
|
||||
Os.close(newSocket);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if the IpSecService holds the reserved port. If
|
||||
* closeUdpEncapsulationSocket is not called, the socket cleanup should not be complete.
|
||||
*/
|
||||
@Test
|
||||
public void testUdpEncapPortNotReleased() throws Exception {
|
||||
IpSecUdpEncapResponse udpEncapResp =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(udpEncapResp);
|
||||
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
|
||||
int localport = udpEncapResp.port;
|
||||
|
||||
udpEncapResp.fileDescriptor.close();
|
||||
|
||||
FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
try {
|
||||
Os.bind(newSocket, INADDR_ANY, localport);
|
||||
fail("ErrnoException not thrown");
|
||||
} catch (ErrnoException e) {
|
||||
assertEquals(EADDRINUSE, e.errno);
|
||||
}
|
||||
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenUdpEncapsulationSocketOnRandomPort() throws Exception {
|
||||
IpSecUdpEncapResponse udpEncapResp =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(udpEncapResp);
|
||||
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
|
||||
assertNotEquals(0, udpEncapResp.port);
|
||||
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
|
||||
udpEncapResp.fileDescriptor.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenUdpEncapsulationSocketPortRange() throws Exception {
|
||||
try {
|
||||
mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_INVALID_PORT, new Binder());
|
||||
fail("IllegalArgumentException not thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT_OUT_RANGE, new Binder());
|
||||
fail("IllegalArgumentException not thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenUdpEncapsulationSocketTwice() throws Exception {
|
||||
IpSecUdpEncapResponse udpEncapResp =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(udpEncapResp);
|
||||
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
|
||||
int localport = udpEncapResp.port;
|
||||
|
||||
IpSecUdpEncapResponse testUdpEncapResp =
|
||||
mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
|
||||
assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, testUdpEncapResp.status);
|
||||
|
||||
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
|
||||
udpEncapResp.fileDescriptor.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseInvalidUdpEncapsulationSocket() throws Exception {
|
||||
try {
|
||||
mIpSecService.closeUdpEncapsulationSocket(1);
|
||||
fail("IllegalArgumentException not thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsAuth() {
|
||||
// Validate that correct algorithm type succeeds
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
config.setAuthentication(AUTH_ALGO);
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
|
||||
// Validate that incorrect algorithm types fails
|
||||
for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) {
|
||||
try {
|
||||
config = new IpSecConfig();
|
||||
config.setAuthentication(algo);
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
fail("Did not throw exception on invalid algorithm type");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsCrypt() {
|
||||
// Validate that correct algorithm type succeeds
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
config.setEncryption(CRYPT_ALGO);
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
|
||||
// Validate that incorrect algorithm types fails
|
||||
for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) {
|
||||
try {
|
||||
config = new IpSecConfig();
|
||||
config.setEncryption(algo);
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
fail("Did not throw exception on invalid algorithm type");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsAead() {
|
||||
// Validate that correct algorithm type succeeds
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
config.setAuthenticatedEncryption(AEAD_ALGO);
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
|
||||
// Validate that incorrect algorithm types fails
|
||||
for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) {
|
||||
try {
|
||||
config = new IpSecConfig();
|
||||
config.setAuthenticatedEncryption(algo);
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
fail("Did not throw exception on invalid algorithm type");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsAuthCrypt() {
|
||||
// Validate that correct algorithm type succeeds
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
config.setAuthentication(AUTH_ALGO);
|
||||
config.setEncryption(CRYPT_ALGO);
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsNoAlgorithms() {
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
try {
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
fail("Expected exception; no algorithms specified");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsAeadWithAuth() {
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
config.setAuthenticatedEncryption(AEAD_ALGO);
|
||||
config.setAuthentication(AUTH_ALGO);
|
||||
try {
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
fail("Expected exception; both AEAD and auth algorithm specified");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsAeadWithCrypt() {
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
config.setAuthenticatedEncryption(AEAD_ALGO);
|
||||
config.setEncryption(CRYPT_ALGO);
|
||||
try {
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
fail("Expected exception; both AEAD and crypt algorithm specified");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateAlgorithmsAeadWithAuthAndCrypt() {
|
||||
IpSecConfig config = new IpSecConfig();
|
||||
config.setAuthenticatedEncryption(AEAD_ALGO);
|
||||
config.setAuthentication(AUTH_ALGO);
|
||||
config.setEncryption(CRYPT_ALGO);
|
||||
try {
|
||||
mIpSecService.validateAlgorithms(config);
|
||||
fail("Expected exception; AEAD, auth and crypt algorithm specified");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteInvalidTransform() throws Exception {
|
||||
try {
|
||||
mIpSecService.deleteTransform(1);
|
||||
fail("IllegalArgumentException not thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveTransportModeTransform() throws Exception {
|
||||
Socket socket = new Socket();
|
||||
socket.bind(null);
|
||||
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
|
||||
mIpSecService.removeTransportModeTransforms(pfd);
|
||||
|
||||
verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateIpAddresses() throws Exception {
|
||||
String[] invalidAddresses =
|
||||
new String[] {"www.google.com", "::", "2001::/64", "0.0.0.0", ""};
|
||||
for (String address : invalidAddresses) {
|
||||
try {
|
||||
IpSecSpiResponse spiResp =
|
||||
mIpSecService.allocateSecurityParameterIndex(
|
||||
address, DROID_SPI, new Binder());
|
||||
fail("Invalid address was passed through IpSecService validation: " + address);
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (Exception e) {
|
||||
fail(
|
||||
"Invalid InetAddress was not caught in validation: "
|
||||
+ address
|
||||
+ ", Exception: "
|
||||
+ e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if the number of encap UDP socket that one UID can reserve has a
|
||||
* reasonable limit.
|
||||
*/
|
||||
@Test
|
||||
public void testSocketResourceTrackerLimitation() throws Exception {
|
||||
List<IpSecUdpEncapResponse> openUdpEncapSockets = new ArrayList<IpSecUdpEncapResponse>();
|
||||
// Reserve sockets until it fails.
|
||||
for (int i = 0; i < MAX_NUM_ENCAP_SOCKETS; i++) {
|
||||
IpSecUdpEncapResponse newUdpEncapSocket =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(newUdpEncapSocket);
|
||||
if (IpSecManager.Status.OK != newUdpEncapSocket.status) {
|
||||
break;
|
||||
}
|
||||
openUdpEncapSockets.add(newUdpEncapSocket);
|
||||
}
|
||||
// Assert that the total sockets quota has a reasonable limit.
|
||||
assertTrue("No UDP encap socket was open", !openUdpEncapSockets.isEmpty());
|
||||
assertTrue(
|
||||
"Number of open UDP encap sockets is out of bound",
|
||||
openUdpEncapSockets.size() < MAX_NUM_ENCAP_SOCKETS);
|
||||
|
||||
// Try to reserve one more UDP encapsulation socket, and should fail.
|
||||
IpSecUdpEncapResponse extraUdpEncapSocket =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(extraUdpEncapSocket);
|
||||
assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, extraUdpEncapSocket.status);
|
||||
|
||||
// Close one of the open UDP encapsulation sockets.
|
||||
mIpSecService.closeUdpEncapsulationSocket(openUdpEncapSockets.get(0).resourceId);
|
||||
openUdpEncapSockets.get(0).fileDescriptor.close();
|
||||
openUdpEncapSockets.remove(0);
|
||||
|
||||
// Try to reserve one more UDP encapsulation socket, and should be successful.
|
||||
extraUdpEncapSocket = mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(extraUdpEncapSocket);
|
||||
assertEquals(IpSecManager.Status.OK, extraUdpEncapSocket.status);
|
||||
openUdpEncapSockets.add(extraUdpEncapSocket);
|
||||
|
||||
// Close open UDP sockets.
|
||||
for (IpSecUdpEncapResponse openSocket : openUdpEncapSockets) {
|
||||
mIpSecService.closeUdpEncapsulationSocket(openSocket.resourceId);
|
||||
openSocket.fileDescriptor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if the number of SPI that one UID can reserve has a reasonable limit.
|
||||
* This test does not test for both address families or duplicate SPIs because resource tracking
|
||||
* code does not depend on them.
|
||||
*/
|
||||
@Test
|
||||
public void testSpiResourceTrackerLimitation() throws Exception {
|
||||
List<IpSecSpiResponse> reservedSpis = new ArrayList<IpSecSpiResponse>();
|
||||
// Return the same SPI for all SPI allocation since IpSecService only
|
||||
// tracks the resource ID.
|
||||
when(mMockNetd.ipSecAllocateSpi(
|
||||
anyInt(),
|
||||
anyString(),
|
||||
eq(InetAddress.getLoopbackAddress().getHostAddress()),
|
||||
anyInt()))
|
||||
.thenReturn(DROID_SPI);
|
||||
// Reserve spis until it fails.
|
||||
for (int i = 0; i < MAX_NUM_SPIS; i++) {
|
||||
IpSecSpiResponse newSpi =
|
||||
mIpSecService.allocateSecurityParameterIndex(
|
||||
InetAddress.getLoopbackAddress().getHostAddress(),
|
||||
DROID_SPI + i,
|
||||
new Binder());
|
||||
assertNotNull(newSpi);
|
||||
if (IpSecManager.Status.OK != newSpi.status) {
|
||||
break;
|
||||
}
|
||||
reservedSpis.add(newSpi);
|
||||
}
|
||||
// Assert that the SPI quota has a reasonable limit.
|
||||
assertTrue(reservedSpis.size() > 0 && reservedSpis.size() < MAX_NUM_SPIS);
|
||||
|
||||
// Try to reserve one more SPI, and should fail.
|
||||
IpSecSpiResponse extraSpi =
|
||||
mIpSecService.allocateSecurityParameterIndex(
|
||||
InetAddress.getLoopbackAddress().getHostAddress(),
|
||||
DROID_SPI + MAX_NUM_SPIS,
|
||||
new Binder());
|
||||
assertNotNull(extraSpi);
|
||||
assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, extraSpi.status);
|
||||
|
||||
// Release one reserved spi.
|
||||
mIpSecService.releaseSecurityParameterIndex(reservedSpis.get(0).resourceId);
|
||||
reservedSpis.remove(0);
|
||||
|
||||
// Should successfully reserve one more spi.
|
||||
extraSpi =
|
||||
mIpSecService.allocateSecurityParameterIndex(
|
||||
InetAddress.getLoopbackAddress().getHostAddress(),
|
||||
DROID_SPI + MAX_NUM_SPIS,
|
||||
new Binder());
|
||||
assertNotNull(extraSpi);
|
||||
assertEquals(IpSecManager.Status.OK, extraSpi.status);
|
||||
|
||||
// Release reserved SPIs.
|
||||
for (IpSecSpiResponse spiResp : reservedSpis) {
|
||||
mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUidFdtagger() throws Exception {
|
||||
SocketTagger actualSocketTagger = SocketTagger.get();
|
||||
|
||||
try {
|
||||
FileDescriptor sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
// Has to be done after socket creation because BlockGuardOS calls tag on new sockets
|
||||
SocketTagger mockSocketTagger = mock(SocketTagger.class);
|
||||
SocketTagger.set(mockSocketTagger);
|
||||
|
||||
mIpSecService.mUidFdTagger.tag(sockFd, Process.LAST_APPLICATION_UID);
|
||||
verify(mockSocketTagger).tag(eq(sockFd));
|
||||
} finally {
|
||||
SocketTagger.set(actualSocketTagger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two file descriptors point to the same file.
|
||||
*
|
||||
* <p>According to stat.h documentation, the correct way to check for equivalent or duplicated
|
||||
* file descriptors is to check their inode and device. These two entries uniquely identify any
|
||||
* file.
|
||||
*/
|
||||
private boolean fileDescriptorsEqual(FileDescriptor fd1, FileDescriptor fd2) {
|
||||
try {
|
||||
StructStat fd1Stat = Os.fstat(fd1);
|
||||
StructStat fd2Stat = Os.fstat(fd2);
|
||||
|
||||
return fd1Stat.st_ino == fd2Stat.st_ino && fd1Stat.st_dev == fd2Stat.st_dev;
|
||||
} catch (ErrnoException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenUdpEncapSocketTagsSocket() throws Exception {
|
||||
IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class);
|
||||
IpSecService testIpSecService = new IpSecService(
|
||||
mMockContext, mMockIpSecSrvConfig, mockTagger);
|
||||
|
||||
IpSecUdpEncapResponse udpEncapResp =
|
||||
testIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
assertNotNull(udpEncapResp);
|
||||
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
|
||||
|
||||
FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
|
||||
ArgumentMatcher<FileDescriptor> fdMatcher =
|
||||
(argFd) -> {
|
||||
return fileDescriptorsEqual(sockFd, argFd);
|
||||
};
|
||||
verify(mockTagger).tag(argThat(fdMatcher), eq(Os.getuid()));
|
||||
|
||||
testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
|
||||
udpEncapResp.fileDescriptor.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner() throws Exception {
|
||||
IpSecUdpEncapResponse udpEncapResp =
|
||||
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
|
||||
|
||||
FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
|
||||
ArgumentMatcher<ParcelFileDescriptor> fdMatcher = (arg) -> {
|
||||
try {
|
||||
StructStat sockStat = Os.fstat(sockFd);
|
||||
StructStat argStat = Os.fstat(arg.getFileDescriptor());
|
||||
|
||||
return sockStat.st_ino == argStat.st_ino
|
||||
&& sockStat.st_dev == argStat.st_dev;
|
||||
} catch (ErrnoException e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));
|
||||
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReserveNetId() {
|
||||
final Range<Integer> netIdRange = ConnectivityManager.getIpSecNetIdRange();
|
||||
for (int netId = netIdRange.getLower(); netId <= netIdRange.getUpper(); netId++) {
|
||||
assertEquals(netId, mIpSecService.reserveNetId());
|
||||
}
|
||||
|
||||
// Check that resource exhaustion triggers an exception
|
||||
try {
|
||||
mIpSecService.reserveNetId();
|
||||
fail("Did not throw error for all netIds reserved");
|
||||
} catch (IllegalStateException expected) {
|
||||
}
|
||||
|
||||
// Now release one and try again
|
||||
int releasedNetId =
|
||||
netIdRange.getLower() + (netIdRange.getUpper() - netIdRange.getLower()) / 2;
|
||||
mIpSecService.releaseNetId(releasedNetId);
|
||||
assertEquals(releasedNetId, mIpSecService.reserveNetId());
|
||||
}
|
||||
}
|
||||
197
tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
Normal file
197
tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
// Don't warn about deprecated types anywhere in this test, because LegacyTypeTracker's very reason
|
||||
// for existence is to power deprecated APIs. The annotation has to apply to the whole file because
|
||||
// otherwise warnings will be generated by the imports of deprecated constants like TYPE_xxx.
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.android.server
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.FEATURE_WIFI
|
||||
import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
|
||||
import android.net.ConnectivityManager.TYPE_ETHERNET
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_CBS
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_DUN
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_FOTA
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_HIPRI
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_IA
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_IMS
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_MMS
|
||||
import android.net.ConnectivityManager.TYPE_MOBILE_SUPL
|
||||
import android.net.ConnectivityManager.TYPE_VPN
|
||||
import android.net.ConnectivityManager.TYPE_WIFI
|
||||
import android.net.ConnectivityManager.TYPE_WIFI_P2P
|
||||
import android.net.ConnectivityManager.TYPE_WIMAX
|
||||
import android.net.EthernetManager
|
||||
import android.net.NetworkInfo.DetailedState.CONNECTED
|
||||
import android.net.NetworkInfo.DetailedState.DISCONNECTED
|
||||
import android.telephony.TelephonyManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.server.ConnectivityService.LegacyTypeTracker
|
||||
import com.android.server.connectivity.NetworkAgentInfo
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
const val UNSUPPORTED_TYPE = TYPE_WIMAX
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class LegacyTypeTrackerTest {
|
||||
private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE,
|
||||
TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI,
|
||||
TYPE_MOBILE_FOTA, TYPE_MOBILE_IMS, TYPE_MOBILE_CBS, TYPE_MOBILE_IA,
|
||||
TYPE_MOBILE_EMERGENCY, TYPE_VPN)
|
||||
|
||||
private val mMockService = mock(ConnectivityService::class.java).apply {
|
||||
doReturn(false).`when`(this).isDefaultNetwork(any())
|
||||
}
|
||||
private val mPm = mock(PackageManager::class.java)
|
||||
private val mContext = mock(Context::class.java).apply {
|
||||
doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI)
|
||||
doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
|
||||
doReturn(mPm).`when`(this).packageManager
|
||||
doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService(
|
||||
Context.ETHERNET_SERVICE)
|
||||
}
|
||||
private val mTm = mock(TelephonyManager::class.java).apply {
|
||||
doReturn(true).`when`(this).isDataCapable
|
||||
}
|
||||
|
||||
private fun makeTracker() = LegacyTypeTracker(mMockService).apply {
|
||||
loadSupportedTypes(mContext, mTm)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSupportedTypes() {
|
||||
val tracker = makeTracker()
|
||||
supportedTypes.forEach {
|
||||
assertTrue(tracker.isTypeSupported(it))
|
||||
}
|
||||
assertFalse(tracker.isTypeSupported(UNSUPPORTED_TYPE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSupportedTypes_NoEthernet() {
|
||||
doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE)
|
||||
assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSupportedTypes_NoTelephony() {
|
||||
doReturn(false).`when`(mTm).isDataCapable
|
||||
val tracker = makeTracker()
|
||||
val nonMobileTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_VPN)
|
||||
nonMobileTypes.forEach {
|
||||
assertTrue(tracker.isTypeSupported(it))
|
||||
}
|
||||
supportedTypes.toSet().minus(nonMobileTypes).forEach {
|
||||
assertFalse(tracker.isTypeSupported(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSupportedTypes_NoWifiDirect() {
|
||||
doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
|
||||
val tracker = makeTracker()
|
||||
assertFalse(tracker.isTypeSupported(TYPE_WIFI_P2P))
|
||||
supportedTypes.toSet().minus(TYPE_WIFI_P2P).forEach {
|
||||
assertTrue(tracker.isTypeSupported(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSupl() {
|
||||
val tracker = makeTracker()
|
||||
val mobileNai = mock(NetworkAgentInfo::class.java)
|
||||
tracker.add(TYPE_MOBILE, mobileNai)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE)
|
||||
reset(mMockService)
|
||||
tracker.add(TYPE_MOBILE_SUPL, mobileNai)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
|
||||
reset(mMockService)
|
||||
tracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
|
||||
reset(mMockService)
|
||||
tracker.add(TYPE_MOBILE_SUPL, mobileNai)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
|
||||
reset(mMockService)
|
||||
tracker.remove(mobileNai, false)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddNetwork() {
|
||||
val tracker = makeTracker()
|
||||
val mobileNai = mock(NetworkAgentInfo::class.java)
|
||||
val wifiNai = mock(NetworkAgentInfo::class.java)
|
||||
tracker.add(TYPE_MOBILE, mobileNai)
|
||||
tracker.add(TYPE_WIFI, wifiNai)
|
||||
assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
|
||||
assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
|
||||
// Make sure adding a second NAI does not change the results.
|
||||
val secondMobileNai = mock(NetworkAgentInfo::class.java)
|
||||
tracker.add(TYPE_MOBILE, secondMobileNai)
|
||||
assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
|
||||
assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
|
||||
// Make sure removing a network that wasn't added for this type is a no-op.
|
||||
tracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
|
||||
assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
|
||||
assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
|
||||
// Remove the top network for mobile and make sure the second one becomes the network
|
||||
// of record for this type.
|
||||
tracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
|
||||
assertSame(tracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
|
||||
assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
|
||||
// Make sure adding a network for an unsupported type does not register it.
|
||||
tracker.add(UNSUPPORTED_TYPE, mobileNai)
|
||||
assertNull(tracker.getNetworkForType(UNSUPPORTED_TYPE))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBroadcastOnDisconnect() {
|
||||
val tracker = makeTracker()
|
||||
val mobileNai1 = mock(NetworkAgentInfo::class.java)
|
||||
val mobileNai2 = mock(NetworkAgentInfo::class.java)
|
||||
doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1)
|
||||
tracker.add(TYPE_MOBILE, mobileNai1)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE)
|
||||
reset(mMockService)
|
||||
doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2)
|
||||
tracker.add(TYPE_MOBILE, mobileNai2)
|
||||
verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt())
|
||||
tracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE)
|
||||
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE)
|
||||
}
|
||||
}
|
||||
53
tests/unit/java/com/android/server/NetIdManagerTest.kt
Normal file
53
tests/unit/java/com/android/server/NetIdManagerTest.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.server
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.server.NetIdManager.MIN_NET_ID
|
||||
import com.android.testutils.assertThrows
|
||||
import com.android.testutils.ExceptionUtils.ThrowingRunnable
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class NetIdManagerTest {
|
||||
@Test
|
||||
fun testReserveReleaseNetId() {
|
||||
val manager = NetIdManager(MIN_NET_ID + 4)
|
||||
assertEquals(MIN_NET_ID, manager.reserveNetId())
|
||||
assertEquals(MIN_NET_ID + 1, manager.reserveNetId())
|
||||
assertEquals(MIN_NET_ID + 2, manager.reserveNetId())
|
||||
assertEquals(MIN_NET_ID + 3, manager.reserveNetId())
|
||||
|
||||
manager.releaseNetId(MIN_NET_ID + 1)
|
||||
manager.releaseNetId(MIN_NET_ID + 3)
|
||||
// IDs only loop once there is no higher ID available
|
||||
assertEquals(MIN_NET_ID + 4, manager.reserveNetId())
|
||||
assertEquals(MIN_NET_ID + 1, manager.reserveNetId())
|
||||
assertEquals(MIN_NET_ID + 3, manager.reserveNetId())
|
||||
assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() })
|
||||
manager.releaseNetId(MIN_NET_ID + 5)
|
||||
// Still no ID available: MIN_NET_ID + 5 was not reserved
|
||||
assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() })
|
||||
manager.releaseNetId(MIN_NET_ID + 2)
|
||||
// Throwing an exception still leaves the manager in a working state
|
||||
assertEquals(MIN_NET_ID + 2, manager.reserveNetId())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.server;
|
||||
|
||||
import static android.util.DebugUtils.valueToString;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.net.INetd;
|
||||
import android.net.INetdUnsolicitedEventListener;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.os.BatteryStats;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.app.IBatteryStats;
|
||||
import com.android.server.NetworkManagementService.Dependencies;
|
||||
import com.android.server.net.BaseNetworkObserver;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Tests for {@link NetworkManagementService}.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetworkManagementServiceTest {
|
||||
private NetworkManagementService mNMService;
|
||||
@Mock private Context mContext;
|
||||
@Mock private IBatteryStats.Stub mBatteryStatsService;
|
||||
@Mock private INetd.Stub mNetdService;
|
||||
|
||||
private static final int TEST_UID = 111;
|
||||
|
||||
@NonNull
|
||||
@Captor
|
||||
private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
|
||||
|
||||
private final MockDependencies mDeps = new MockDependencies();
|
||||
|
||||
private final class MockDependencies extends Dependencies {
|
||||
@Override
|
||||
public IBinder getService(String name) {
|
||||
switch (name) {
|
||||
case BatteryStats.SERVICE_NAME:
|
||||
return mBatteryStatsService;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown service " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerLocalService(NetworkManagementInternal nmi) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public INetd getNetd() {
|
||||
return mNetdService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCallingUid() {
|
||||
return Process.SYSTEM_UID;
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doNothing().when(mNetdService)
|
||||
.registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
|
||||
// Start the service and wait until it connects to our socket.
|
||||
mNMService = NetworkManagementService.create(mContext, mDeps);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
mNMService.shutdown();
|
||||
}
|
||||
|
||||
private static <T> T expectSoon(T mock) {
|
||||
return verify(mock, timeout(200));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that network observers work properly.
|
||||
*/
|
||||
@Test
|
||||
public void testNetworkObservers() throws Exception {
|
||||
BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
|
||||
doReturn(new Binder()).when(observer).asBinder(); // Used by registerObserver.
|
||||
mNMService.registerObserver(observer);
|
||||
|
||||
// Forget everything that happened to the mock so far, so we can explicitly verify
|
||||
// everything that happens and does not happen to it from now on.
|
||||
|
||||
INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
|
||||
reset(observer);
|
||||
// Now call unsolListener methods and ensure that the observer methods are
|
||||
// called. After every method we expect a callback soon after; to ensure that
|
||||
// invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
|
||||
|
||||
/**
|
||||
* Interface changes.
|
||||
*/
|
||||
unsolListener.onInterfaceAdded("rmnet12");
|
||||
expectSoon(observer).interfaceAdded("rmnet12");
|
||||
|
||||
unsolListener.onInterfaceRemoved("eth1");
|
||||
expectSoon(observer).interfaceRemoved("eth1");
|
||||
|
||||
unsolListener.onInterfaceChanged("clat4", true);
|
||||
expectSoon(observer).interfaceStatusChanged("clat4", true);
|
||||
|
||||
unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
|
||||
expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
|
||||
|
||||
/**
|
||||
* Bandwidth control events.
|
||||
*/
|
||||
unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
|
||||
expectSoon(observer).limitReached("data", "rmnet_usb0");
|
||||
|
||||
/**
|
||||
* Interface class activity.
|
||||
*/
|
||||
unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
|
||||
expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
|
||||
|
||||
unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
|
||||
expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
|
||||
|
||||
unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
|
||||
expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
|
||||
|
||||
/**
|
||||
* IP address changes.
|
||||
*/
|
||||
unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
|
||||
expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
|
||||
|
||||
unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
|
||||
expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
|
||||
|
||||
unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
|
||||
expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
|
||||
|
||||
/**
|
||||
* DNS information broadcasts.
|
||||
*/
|
||||
unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
|
||||
expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
|
||||
new String[]{"2001:db8::1"});
|
||||
|
||||
unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
|
||||
new String[]{"2001:db8::1", "2001:db8::2"});
|
||||
expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
|
||||
new String[]{"2001:db8::1", "2001:db8::2"});
|
||||
|
||||
// We don't check for negative lifetimes, only for parse errors.
|
||||
unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
|
||||
expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
|
||||
new String[]{"::1"});
|
||||
|
||||
// No syntax checking on the addresses.
|
||||
unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
|
||||
new String[]{"", "::", "", "foo", "::1"});
|
||||
expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
|
||||
new String[]{"", "::", "", "foo", "::1"});
|
||||
|
||||
// Make sure nothing else was called.
|
||||
verifyNoMoreInteractions(observer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirewallEnabled() {
|
||||
mNMService.setFirewallEnabled(true);
|
||||
assertTrue(mNMService.isFirewallEnabled());
|
||||
|
||||
mNMService.setFirewallEnabled(false);
|
||||
assertFalse(mNMService.isFirewallEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkRestrictedDefault() {
|
||||
assertFalse(mNMService.isNetworkRestricted(TEST_UID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMeteredNetworkRestrictions() throws RemoteException {
|
||||
// Make sure the mocked netd method returns true.
|
||||
doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
|
||||
|
||||
// Restrict usage of mobile data in background
|
||||
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
|
||||
assertTrue("Should be true since mobile data usage is restricted",
|
||||
mNMService.isNetworkRestricted(TEST_UID));
|
||||
|
||||
mNMService.setDataSaverModeEnabled(true);
|
||||
verify(mNetdService).bandwidthEnableDataSaver(true);
|
||||
|
||||
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
|
||||
assertTrue("Should be true since data saver is on and the uid is not allowlisted",
|
||||
mNMService.isNetworkRestricted(TEST_UID));
|
||||
|
||||
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
|
||||
assertFalse("Should be false since data saver is on and the uid is allowlisted",
|
||||
mNMService.isNetworkRestricted(TEST_UID));
|
||||
|
||||
// remove uid from allowlist and turn datasaver off again
|
||||
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
|
||||
mNMService.setDataSaverModeEnabled(false);
|
||||
verify(mNetdService).bandwidthEnableDataSaver(false);
|
||||
assertFalse("Network should not be restricted when data saver is off",
|
||||
mNMService.isNetworkRestricted(TEST_UID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirewallChains() {
|
||||
final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
|
||||
// Dozable chain
|
||||
final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
|
||||
isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
|
||||
isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
|
||||
isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
|
||||
expected.put(INetd.FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
|
||||
// Powersaver chain
|
||||
final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
|
||||
isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
|
||||
isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
|
||||
isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
|
||||
expected.put(INetd.FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
|
||||
// Standby chain
|
||||
final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
|
||||
isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
|
||||
isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
|
||||
isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
|
||||
expected.put(INetd.FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
|
||||
// Restricted mode chain
|
||||
final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
|
||||
isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
|
||||
isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
|
||||
isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
|
||||
expected.put(INetd.FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
|
||||
|
||||
final int[] chains = {
|
||||
INetd.FIREWALL_CHAIN_STANDBY,
|
||||
INetd.FIREWALL_CHAIN_POWERSAVE,
|
||||
INetd.FIREWALL_CHAIN_DOZABLE,
|
||||
INetd.FIREWALL_CHAIN_RESTRICTED
|
||||
};
|
||||
final int[] states = {
|
||||
INetd.FIREWALL_RULE_ALLOW,
|
||||
INetd.FIREWALL_RULE_DENY,
|
||||
NetworkPolicyManager.FIREWALL_RULE_DEFAULT
|
||||
};
|
||||
BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
|
||||
return String.format("Unexpected value for chain: %s and state: %s",
|
||||
valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
|
||||
valueToString(INetd.class, "FIREWALL_RULE_", state));
|
||||
};
|
||||
for (int chain : chains) {
|
||||
final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
|
||||
mNMService.setFirewallChainEnabled(chain, true);
|
||||
for (int state : states) {
|
||||
mNMService.setFirewallUidRule(chain, TEST_UID, state);
|
||||
assertEquals(errorMsg.apply(chain, state),
|
||||
expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
|
||||
}
|
||||
mNMService.setFirewallChainEnabled(chain, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
194
tests/unit/java/com/android/server/NsdServiceTest.java
Normal file
194
tests/unit/java/com/android/server/NsdServiceTest.java
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.server;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.NsdService.DaemonConnection;
|
||||
import com.android.server.NsdService.DaemonConnectionSupplier;
|
||||
import com.android.server.NsdService.NativeCallbackReceiver;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
// TODOs:
|
||||
// - test client can send requests and receive replies
|
||||
// - test NSD_ON ENABLE/DISABLED listening
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NsdServiceTest {
|
||||
|
||||
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
|
||||
|
||||
long mTimeoutMs = 100; // non-final so that tests can adjust the value.
|
||||
|
||||
@Mock Context mContext;
|
||||
@Mock ContentResolver mResolver;
|
||||
@Mock NsdService.NsdSettings mSettings;
|
||||
@Mock DaemonConnection mDaemon;
|
||||
NativeCallbackReceiver mDaemonCallback;
|
||||
HandlerThread mThread;
|
||||
TestHandler mHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mThread = new HandlerThread("mock-service-handler");
|
||||
mThread.start();
|
||||
mHandler = new TestHandler(mThread.getLooper());
|
||||
when(mContext.getContentResolver()).thenReturn(mResolver);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (mThread != null) {
|
||||
mThread.quit();
|
||||
mThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientsCanConnectAndDisconnect() {
|
||||
when(mSettings.isEnabled()).thenReturn(true);
|
||||
|
||||
NsdService service = makeService();
|
||||
|
||||
NsdManager client1 = connectClient(service);
|
||||
verify(mDaemon, timeout(100).times(1)).start();
|
||||
|
||||
NsdManager client2 = connectClient(service);
|
||||
|
||||
client1.disconnect();
|
||||
client2.disconnect();
|
||||
|
||||
verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
|
||||
|
||||
client1.disconnect();
|
||||
client2.disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientRequestsAreGCedAtDisconnection() {
|
||||
when(mSettings.isEnabled()).thenReturn(true);
|
||||
when(mDaemon.execute(any())).thenReturn(true);
|
||||
|
||||
NsdService service = makeService();
|
||||
NsdManager client = connectClient(service);
|
||||
|
||||
verify(mDaemon, timeout(100).times(1)).start();
|
||||
|
||||
NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
|
||||
request.setPort(2201);
|
||||
|
||||
// Client registration request
|
||||
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
|
||||
client.registerService(request, PROTOCOL, listener1);
|
||||
verifyDaemonCommand("register 2 a_name a_type 2201");
|
||||
|
||||
// Client discovery request
|
||||
NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
|
||||
client.discoverServices("a_type", PROTOCOL, listener2);
|
||||
verifyDaemonCommand("discover 3 a_type");
|
||||
|
||||
// Client resolve request
|
||||
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
|
||||
client.resolveService(request, listener3);
|
||||
verifyDaemonCommand("resolve 4 a_name a_type local.");
|
||||
|
||||
// Client disconnects
|
||||
client.disconnect();
|
||||
verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
|
||||
|
||||
// checks that request are cleaned
|
||||
verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
|
||||
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
NsdService makeService() {
|
||||
DaemonConnectionSupplier supplier = (callback) -> {
|
||||
mDaemonCallback = callback;
|
||||
return mDaemon;
|
||||
};
|
||||
NsdService service = new NsdService(mContext, mSettings, mHandler, supplier);
|
||||
verify(mDaemon, never()).execute(any(String.class));
|
||||
return service;
|
||||
}
|
||||
|
||||
NsdManager connectClient(NsdService service) {
|
||||
return new NsdManager(mContext, service);
|
||||
}
|
||||
|
||||
void verifyDaemonCommands(String... wants) {
|
||||
verifyDaemonCommand(String.join(" ", wants), wants.length);
|
||||
}
|
||||
|
||||
void verifyDaemonCommand(String want) {
|
||||
verifyDaemonCommand(want, 1);
|
||||
}
|
||||
|
||||
void verifyDaemonCommand(String want, int n) {
|
||||
ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture());
|
||||
String got = "";
|
||||
for (Object o : argumentsCaptor.getAllValues()) {
|
||||
got += o + " ";
|
||||
}
|
||||
assertEquals(want, got.trim());
|
||||
// rearm deamon for next command verification
|
||||
reset(mDaemon);
|
||||
when(mDaemon.execute(any())).thenReturn(true);
|
||||
}
|
||||
|
||||
public static class TestHandler extends Handler {
|
||||
public Message lastMessage;
|
||||
|
||||
TestHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
lastMessage = obtainMessage();
|
||||
lastMessage.copyFrom(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* Copyright (C) 2018, 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.server.connectivity;
|
||||
|
||||
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
|
||||
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
|
||||
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
|
||||
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
|
||||
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER;
|
||||
import static android.net.NetworkCapabilities.MAX_TRANSPORT;
|
||||
import static android.net.NetworkCapabilities.MIN_TRANSPORT;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
||||
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
|
||||
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
|
||||
|
||||
import static com.android.testutils.MiscAsserts.assertContainsExactly;
|
||||
import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
|
||||
import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
|
||||
|
||||
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.assertTrue;
|
||||
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.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivitySettingsManager;
|
||||
import android.net.IDnsResolver;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.ResolverOptionsParcel;
|
||||
import android.net.ResolverParamsParcel;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.shared.PrivateDnsConfig;
|
||||
import android.provider.Settings;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.util.MessageUtils;
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
|
||||
import libcore.net.InetAddressUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Tests for {@link DnsManager}.
|
||||
*
|
||||
* Build, install and run with:
|
||||
* runtest frameworks-net -c com.android.server.connectivity.DnsManagerTest
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class DnsManagerTest {
|
||||
static final String TEST_IFACENAME = "test_wlan0";
|
||||
static final int TEST_NETID = 100;
|
||||
static final int TEST_NETID_ALTERNATE = 101;
|
||||
static final int TEST_NETID_UNTRACKED = 102;
|
||||
static final int TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
|
||||
static final int TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25;
|
||||
static final int TEST_DEFAULT_MIN_SAMPLES = 8;
|
||||
static final int TEST_DEFAULT_MAX_SAMPLES = 64;
|
||||
static final int[] TEST_TRANSPORT_TYPES = {TRANSPORT_WIFI, TRANSPORT_VPN};
|
||||
|
||||
DnsManager mDnsManager;
|
||||
MockContentResolver mContentResolver;
|
||||
|
||||
@Mock Context mCtx;
|
||||
@Mock IDnsResolver mMockDnsResolver;
|
||||
|
||||
private void assertResolverOptionsEquals(
|
||||
@NonNull ResolverOptionsParcel actual,
|
||||
@NonNull ResolverOptionsParcel expected) {
|
||||
assertEquals(actual.hosts, expected.hosts);
|
||||
assertEquals(actual.tcMode, expected.tcMode);
|
||||
assertEquals(actual.enforceDnsUid, expected.enforceDnsUid);
|
||||
assertFieldCountEquals(3, ResolverOptionsParcel.class);
|
||||
}
|
||||
|
||||
private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual,
|
||||
@NonNull ResolverParamsParcel expected) {
|
||||
assertEquals(actual.netId, expected.netId);
|
||||
assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds);
|
||||
assertEquals(actual.successThreshold, expected.successThreshold);
|
||||
assertEquals(actual.minSamples, expected.minSamples);
|
||||
assertEquals(actual.maxSamples, expected.maxSamples);
|
||||
assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec);
|
||||
assertEquals(actual.retryCount, expected.retryCount);
|
||||
assertContainsStringsExactly(actual.servers, expected.servers);
|
||||
assertContainsStringsExactly(actual.domains, expected.domains);
|
||||
assertEquals(actual.tlsName, expected.tlsName);
|
||||
assertContainsStringsExactly(actual.tlsServers, expected.tlsServers);
|
||||
assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints);
|
||||
assertEquals(actual.caCertificate, expected.caCertificate);
|
||||
assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
|
||||
assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
|
||||
assertContainsExactly(actual.transportTypes, expected.transportTypes);
|
||||
assertFieldCountEquals(16, ResolverParamsParcel.class);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContentResolver = new MockContentResolver();
|
||||
mContentResolver.addProvider(Settings.AUTHORITY,
|
||||
new FakeSettingsProvider());
|
||||
when(mCtx.getContentResolver()).thenReturn(mContentResolver);
|
||||
mDnsManager = new DnsManager(mCtx, mMockDnsResolver);
|
||||
|
||||
// Clear the private DNS settings
|
||||
Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, "");
|
||||
Settings.Global.putString(mContentResolver, PRIVATE_DNS_MODE, "");
|
||||
Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrackedValidationUpdates() throws Exception {
|
||||
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
|
||||
mDnsManager.getPrivateDnsConfig());
|
||||
mDnsManager.updatePrivateDns(new Network(TEST_NETID_ALTERNATE),
|
||||
mDnsManager.getPrivateDnsConfig());
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName(TEST_IFACENAME);
|
||||
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
|
||||
lp.addDnsServer(InetAddress.getByName("4.4.4.4"));
|
||||
|
||||
// Send a validation event that is tracked on the alternate netId
|
||||
mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
|
||||
mDnsManager.flushVmDnsCache();
|
||||
mDnsManager.updateTransportsForNetwork(TEST_NETID_ALTERNATE, TEST_TRANSPORT_TYPES);
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID_ALTERNATE, lp);
|
||||
mDnsManager.flushVmDnsCache();
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE,
|
||||
InetAddress.parseNumericAddress("4.4.4.4"), "",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
LinkProperties fixedLp = new LinkProperties(lp);
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
|
||||
assertFalse(fixedLp.isPrivateDnsActive());
|
||||
assertNull(fixedLp.getPrivateDnsServerName());
|
||||
fixedLp = new LinkProperties(lp);
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID_ALTERNATE, fixedLp);
|
||||
assertTrue(fixedLp.isPrivateDnsActive());
|
||||
assertNull(fixedLp.getPrivateDnsServerName());
|
||||
assertEquals(Arrays.asList(InetAddress.getByName("4.4.4.4")),
|
||||
fixedLp.getValidatedPrivateDnsServers());
|
||||
|
||||
// Set up addresses for strict mode and switch to it.
|
||||
lp.addLinkAddress(new LinkAddress("192.0.2.4/24"));
|
||||
lp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"),
|
||||
TEST_IFACENAME));
|
||||
lp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
|
||||
lp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"),
|
||||
TEST_IFACENAME));
|
||||
|
||||
ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
|
||||
ConnectivitySettingsManager.setPrivateDnsHostname(mCtx, "strictmode.com");
|
||||
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
|
||||
new PrivateDnsConfig("strictmode.com", new InetAddress[] {
|
||||
InetAddress.parseNumericAddress("6.6.6.6"),
|
||||
InetAddress.parseNumericAddress("2001:db8:66:66::1")
|
||||
}));
|
||||
mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
|
||||
mDnsManager.flushVmDnsCache();
|
||||
fixedLp = new LinkProperties(lp);
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
|
||||
assertTrue(fixedLp.isPrivateDnsActive());
|
||||
assertEquals("strictmode.com", fixedLp.getPrivateDnsServerName());
|
||||
// No validation events yet.
|
||||
assertEquals(Arrays.asList(new InetAddress[0]), fixedLp.getValidatedPrivateDnsServers());
|
||||
// Validate one.
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
fixedLp = new LinkProperties(lp);
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
|
||||
assertEquals(Arrays.asList(InetAddress.parseNumericAddress("6.6.6.6")),
|
||||
fixedLp.getValidatedPrivateDnsServers());
|
||||
// Validate the 2nd one.
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
fixedLp = new LinkProperties(lp);
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
|
||||
assertEquals(Arrays.asList(
|
||||
InetAddress.parseNumericAddress("2001:db8:66:66::1"),
|
||||
InetAddress.parseNumericAddress("6.6.6.6")),
|
||||
fixedLp.getValidatedPrivateDnsServers());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreUntrackedValidationUpdates() throws Exception {
|
||||
// The PrivateDnsConfig map is empty, so no validation events will
|
||||
// be tracked.
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
|
||||
mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
|
||||
mDnsManager.flushVmDnsCache();
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("3.3.3.3"), "",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertNull(lp.getPrivateDnsServerName());
|
||||
|
||||
// Validation event has untracked netId
|
||||
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
|
||||
mDnsManager.getPrivateDnsConfig());
|
||||
mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
|
||||
mDnsManager.flushVmDnsCache();
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED,
|
||||
InetAddress.parseNumericAddress("3.3.3.3"), "",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertNull(lp.getPrivateDnsServerName());
|
||||
|
||||
// Validation event has untracked ipAddress
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("4.4.4.4"), "",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertNull(lp.getPrivateDnsServerName());
|
||||
|
||||
// Validation event has untracked hostname
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("3.3.3.3"), "hostname",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertNull(lp.getPrivateDnsServerName());
|
||||
|
||||
// Validation event failed
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("3.3.3.3"), "",
|
||||
VALIDATION_RESULT_FAILURE));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertNull(lp.getPrivateDnsServerName());
|
||||
|
||||
// Network removed
|
||||
mDnsManager.removeNetwork(new Network(TEST_NETID));
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("3.3.3.3"), "", VALIDATION_RESULT_SUCCESS));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertNull(lp.getPrivateDnsServerName());
|
||||
|
||||
// Turn private DNS mode off
|
||||
ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_OFF);
|
||||
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
|
||||
mDnsManager.getPrivateDnsConfig());
|
||||
mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
|
||||
mDnsManager.flushVmDnsCache();
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
|
||||
InetAddress.parseNumericAddress("3.3.3.3"), "",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertNull(lp.getPrivateDnsServerName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverrideDefaultMode() throws Exception {
|
||||
// Hard-coded default is opportunistic mode.
|
||||
final PrivateDnsConfig cfgAuto = DnsManager.getPrivateDnsConfig(mCtx);
|
||||
assertTrue(cfgAuto.useTls);
|
||||
assertEquals("", cfgAuto.hostname);
|
||||
assertEquals(new InetAddress[0], cfgAuto.ips);
|
||||
|
||||
// Pretend a gservices push sets the default to "off".
|
||||
ConnectivitySettingsManager.setPrivateDnsDefaultMode(mCtx, PRIVATE_DNS_MODE_OFF);
|
||||
final PrivateDnsConfig cfgOff = DnsManager.getPrivateDnsConfig(mCtx);
|
||||
assertFalse(cfgOff.useTls);
|
||||
assertEquals("", cfgOff.hostname);
|
||||
assertEquals(new InetAddress[0], cfgOff.ips);
|
||||
|
||||
// Strict mode still works.
|
||||
ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
|
||||
ConnectivitySettingsManager.setPrivateDnsHostname(mCtx, "strictmode.com");
|
||||
final PrivateDnsConfig cfgStrict = DnsManager.getPrivateDnsConfig(mCtx);
|
||||
assertTrue(cfgStrict.useTls);
|
||||
assertEquals("strictmode.com", cfgStrict.hostname);
|
||||
assertEquals(new InetAddress[0], cfgStrict.ips);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendDnsConfiguration() throws Exception {
|
||||
reset(mMockDnsResolver);
|
||||
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
|
||||
mDnsManager.getPrivateDnsConfig());
|
||||
final LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName(TEST_IFACENAME);
|
||||
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
|
||||
lp.addDnsServer(InetAddress.getByName("4.4.4.4"));
|
||||
mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
|
||||
mDnsManager.flushVmDnsCache();
|
||||
|
||||
final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor =
|
||||
ArgumentCaptor.forClass(ResolverParamsParcel.class);
|
||||
verify(mMockDnsResolver, times(1)).setResolverConfiguration(
|
||||
resolverParamsParcelCaptor.capture());
|
||||
final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue();
|
||||
final ResolverParamsParcel expectedParams = new ResolverParamsParcel();
|
||||
expectedParams.netId = TEST_NETID;
|
||||
expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS;
|
||||
expectedParams.successThreshold = TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
|
||||
expectedParams.minSamples = TEST_DEFAULT_MIN_SAMPLES;
|
||||
expectedParams.maxSamples = TEST_DEFAULT_MAX_SAMPLES;
|
||||
expectedParams.servers = new String[]{"3.3.3.3", "4.4.4.4"};
|
||||
expectedParams.domains = new String[]{};
|
||||
expectedParams.tlsName = "";
|
||||
expectedParams.tlsServers = new String[]{"3.3.3.3", "4.4.4.4"};
|
||||
expectedParams.transportTypes = TEST_TRANSPORT_TYPES;
|
||||
expectedParams.resolverOptions = new ResolverOptionsParcel();
|
||||
assertResolverParamsEquals(actualParams, expectedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportTypesEqual() throws Exception {
|
||||
SparseArray<String> ncTransTypes = MessageUtils.findMessageNames(
|
||||
new Class[] { NetworkCapabilities.class }, new String[]{ "TRANSPORT_" });
|
||||
SparseArray<String> dnsTransTypes = MessageUtils.findMessageNames(
|
||||
new Class[] { IDnsResolver.class }, new String[]{ "TRANSPORT_" });
|
||||
assertEquals(0, MIN_TRANSPORT);
|
||||
assertEquals(MAX_TRANSPORT + 1, ncTransTypes.size());
|
||||
// TRANSPORT_UNKNOWN in IDnsResolver is defined to -1 and only for resolver.
|
||||
assertEquals("TRANSPORT_UNKNOWN", dnsTransTypes.get(-1));
|
||||
assertEquals(ncTransTypes.size(), dnsTransTypes.size() - 1);
|
||||
for (int i = MIN_TRANSPORT; i < MAX_TRANSPORT; i++) {
|
||||
String name = ncTransTypes.get(i, null);
|
||||
assertNotNull("Could not find NetworkCapabilies.TRANSPORT_* constant equal to "
|
||||
+ i, name);
|
||||
assertEquals(name, dnsTransTypes.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPrivateDnsConfigForNetwork() throws Exception {
|
||||
final Network network = new Network(TEST_NETID);
|
||||
final InetAddress dnsAddr = InetAddressUtils.parseNumericAddress("3.3.3.3");
|
||||
final InetAddress[] tlsAddrs = new InetAddress[]{
|
||||
InetAddressUtils.parseNumericAddress("6.6.6.6"),
|
||||
InetAddressUtils.parseNumericAddress("2001:db8:66:66::1")
|
||||
};
|
||||
final String tlsName = "strictmode.com";
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.addDnsServer(dnsAddr);
|
||||
|
||||
// The PrivateDnsConfig map is empty, so the default PRIVATE_DNS_OFF is returned.
|
||||
PrivateDnsConfig privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
|
||||
assertFalse(privateDnsCfg.useTls);
|
||||
assertEquals("", privateDnsCfg.hostname);
|
||||
assertEquals(new InetAddress[0], privateDnsCfg.ips);
|
||||
|
||||
// An entry with default PrivateDnsConfig is added to the PrivateDnsConfig map.
|
||||
mDnsManager.updatePrivateDns(network, mDnsManager.getPrivateDnsConfig());
|
||||
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
|
||||
mDnsManager.updatePrivateDnsValidation(
|
||||
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "",
|
||||
VALIDATION_RESULT_SUCCESS));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
|
||||
assertTrue(privateDnsCfg.useTls);
|
||||
assertEquals("", privateDnsCfg.hostname);
|
||||
assertEquals(new InetAddress[0], privateDnsCfg.ips);
|
||||
|
||||
// The original entry is overwritten by a new PrivateDnsConfig.
|
||||
mDnsManager.updatePrivateDns(network, new PrivateDnsConfig(tlsName, tlsAddrs));
|
||||
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
|
||||
privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
|
||||
assertTrue(privateDnsCfg.useTls);
|
||||
assertEquals(tlsName, privateDnsCfg.hostname);
|
||||
assertEquals(tlsAddrs, privateDnsCfg.ips);
|
||||
|
||||
// The network is removed, so the PrivateDnsConfig map becomes empty again.
|
||||
mDnsManager.removeNetwork(network);
|
||||
privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
|
||||
assertFalse(privateDnsCfg.useTls);
|
||||
assertEquals("", privateDnsCfg.hostname);
|
||||
assertEquals(new InetAddress[0], privateDnsCfg.ips);
|
||||
}
|
||||
}
|
||||
134
tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
Normal file
134
tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.server.connectivity
|
||||
|
||||
import android.net.NetworkAgentConfig
|
||||
import android.net.NetworkCapabilities
|
||||
import android.text.TextUtils
|
||||
import android.util.ArraySet
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY
|
||||
import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED
|
||||
import com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED
|
||||
import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
|
||||
import com.android.server.connectivity.FullScore.POLICY_IS_VPN
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.collections.minOfOrNull
|
||||
import kotlin.collections.maxOfOrNull
|
||||
import kotlin.reflect.full.staticProperties
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class FullScoreTest {
|
||||
// Convenience methods
|
||||
fun FullScore.withPolicies(
|
||||
validated: Boolean = false,
|
||||
vpn: Boolean = false,
|
||||
onceChosen: Boolean = false,
|
||||
acceptUnvalidated: Boolean = false
|
||||
): FullScore {
|
||||
val nac = NetworkAgentConfig.Builder().apply {
|
||||
setUnvalidatedConnectivityAcceptable(acceptUnvalidated)
|
||||
setExplicitlySelected(onceChosen)
|
||||
}.build()
|
||||
val nc = NetworkCapabilities.Builder().apply {
|
||||
if (vpn) addTransportType(NetworkCapabilities.TRANSPORT_VPN)
|
||||
if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
}.build()
|
||||
return mixInScore(nc, nac)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetLegacyInt() {
|
||||
val ns = FullScore(50, 0L /* policy */)
|
||||
assertEquals(10, ns.legacyInt) // -40 penalty for not being validated
|
||||
assertEquals(50, ns.legacyIntAsValidated)
|
||||
|
||||
val vpnNs = FullScore(101, 0L /* policy */).withPolicies(vpn = true)
|
||||
assertEquals(101, vpnNs.legacyInt) // VPNs are not subject to unvalidation penalty
|
||||
assertEquals(101, vpnNs.legacyIntAsValidated)
|
||||
assertEquals(101, vpnNs.withPolicies(validated = true).legacyInt)
|
||||
assertEquals(101, vpnNs.withPolicies(validated = true).legacyIntAsValidated)
|
||||
|
||||
val validatedNs = ns.withPolicies(validated = true)
|
||||
assertEquals(50, validatedNs.legacyInt) // No penalty, this is validated
|
||||
assertEquals(50, validatedNs.legacyIntAsValidated)
|
||||
|
||||
val chosenNs = ns.withPolicies(onceChosen = true)
|
||||
assertEquals(10, chosenNs.legacyInt)
|
||||
assertEquals(100, chosenNs.legacyIntAsValidated)
|
||||
assertEquals(10, chosenNs.withPolicies(acceptUnvalidated = true).legacyInt)
|
||||
assertEquals(50, chosenNs.withPolicies(acceptUnvalidated = true).legacyIntAsValidated)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToString() {
|
||||
val string = FullScore(10, 0L /* policy */)
|
||||
.withPolicies(vpn = true, acceptUnvalidated = true).toString()
|
||||
assertTrue(string.contains("Score(10"), string)
|
||||
assertTrue(string.contains("ACCEPT_UNVALIDATED"), string)
|
||||
assertTrue(string.contains("IS_VPN"), string)
|
||||
assertFalse(string.contains("IS_VALIDATED"), string)
|
||||
val foundNames = ArraySet<String>()
|
||||
getAllPolicies().forEach {
|
||||
val name = FullScore.policyNameOf(it.get() as Int)
|
||||
assertFalse(TextUtils.isEmpty(name))
|
||||
assertFalse(foundNames.contains(name))
|
||||
foundNames.add(name)
|
||||
}
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
FullScore.policyNameOf(MAX_CS_MANAGED_POLICY + 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllPolicies() = Regex("POLICY_.*").let { nameRegex ->
|
||||
FullScore::class.staticProperties.filter { it.name.matches(nameRegex) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasPolicy() {
|
||||
val ns = FullScore(50, 0L /* policy */)
|
||||
assertFalse(ns.hasPolicy(POLICY_IS_VALIDATED))
|
||||
assertFalse(ns.hasPolicy(POLICY_IS_VPN))
|
||||
assertFalse(ns.hasPolicy(POLICY_EVER_USER_SELECTED))
|
||||
assertFalse(ns.hasPolicy(POLICY_ACCEPT_UNVALIDATED))
|
||||
assertTrue(ns.withPolicies(validated = true).hasPolicy(POLICY_IS_VALIDATED))
|
||||
assertTrue(ns.withPolicies(vpn = true).hasPolicy(POLICY_IS_VPN))
|
||||
assertTrue(ns.withPolicies(onceChosen = true).hasPolicy(POLICY_EVER_USER_SELECTED))
|
||||
assertTrue(ns.withPolicies(acceptUnvalidated = true).hasPolicy(POLICY_ACCEPT_UNVALIDATED))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMinMaxPolicyConstants() {
|
||||
val policies = getAllPolicies()
|
||||
|
||||
policies.forEach { policy ->
|
||||
assertTrue(policy.get() as Int >= FullScore.MIN_CS_MANAGED_POLICY)
|
||||
assertTrue(policy.get() as Int <= FullScore.MAX_CS_MANAGED_POLICY)
|
||||
}
|
||||
assertEquals(FullScore.MIN_CS_MANAGED_POLICY,
|
||||
policies.minOfOrNull { it.get() as Int })
|
||||
assertEquals(FullScore.MAX_CS_MANAGED_POLICY,
|
||||
policies.maxOfOrNull { it.get() as Int })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.server.connectivity;
|
||||
|
||||
import static com.android.server.connectivity.MetricsTestUtil.aLong;
|
||||
import static com.android.server.connectivity.MetricsTestUtil.aString;
|
||||
import static com.android.server.connectivity.MetricsTestUtil.aType;
|
||||
import static com.android.server.connectivity.MetricsTestUtil.anInt;
|
||||
import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
|
||||
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.BLUETOOTH;
|
||||
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.CELLULAR;
|
||||
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
|
||||
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
|
||||
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.net.ConnectivityMetricsEvent;
|
||||
import android.net.metrics.ApfProgramEvent;
|
||||
import android.net.metrics.ApfStats;
|
||||
import android.net.metrics.DefaultNetworkEvent;
|
||||
import android.net.metrics.DhcpClientEvent;
|
||||
import android.net.metrics.DhcpErrorEvent;
|
||||
import android.net.metrics.IpManagerEvent;
|
||||
import android.net.metrics.IpReachabilityEvent;
|
||||
import android.net.metrics.NetworkEvent;
|
||||
import android.net.metrics.RaEvent;
|
||||
import android.net.metrics.ValidationProbeEvent;
|
||||
import android.net.metrics.WakeupStats;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class IpConnectivityEventBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testLinkLayerInferrence() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(IpReachabilityEvent.class),
|
||||
anInt(IpReachabilityEvent.NUD_FAILED));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
verifySerialization(want, ev);
|
||||
|
||||
ev.netId = 123;
|
||||
ev.transports = 3; // transports have priority for inferrence of link layer
|
||||
ev.ifname = "wlan0";
|
||||
want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
String.format(" link_layer: %d", MULTIPLE),
|
||||
" network_id: 123",
|
||||
" time_ms: 1",
|
||||
" transports: 3",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
verifySerialization(want, ev);
|
||||
|
||||
ev.transports = 1;
|
||||
ev.ifname = null;
|
||||
want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
String.format(" link_layer: %d", CELLULAR),
|
||||
" network_id: 123",
|
||||
" time_ms: 1",
|
||||
" transports: 1",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
verifySerialization(want, ev);
|
||||
|
||||
ev.transports = 0;
|
||||
ev.ifname = "not_inferred";
|
||||
want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"not_inferred\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 123",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
verifySerialization(want, ev);
|
||||
|
||||
ev.ifname = "bt-pan";
|
||||
want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
String.format(" link_layer: %d", BLUETOOTH),
|
||||
" network_id: 123",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
verifySerialization(want, ev);
|
||||
|
||||
ev.ifname = "rmnet_ipa0";
|
||||
want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
String.format(" link_layer: %d", CELLULAR),
|
||||
" network_id: 123",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
verifySerialization(want, ev);
|
||||
|
||||
ev.ifname = "wlan0";
|
||||
want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
String.format(" link_layer: %d", WIFI),
|
||||
" network_id: 123",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultNetworkEventSerialization() {
|
||||
DefaultNetworkEvent ev = new DefaultNetworkEvent(1001);
|
||||
ev.netId = 102;
|
||||
ev.transports = 2;
|
||||
ev.previousTransports = 4;
|
||||
ev.ipv4 = true;
|
||||
ev.initialScore = 20;
|
||||
ev.finalScore = 60;
|
||||
ev.durationMs = 54;
|
||||
ev.validatedMs = 27;
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 102",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 54",
|
||||
" final_score: 60",
|
||||
" initial_score: 20",
|
||||
" ip_support: 1",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 1",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 27",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, IpConnectivityEventBuilder.toProto(ev));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDhcpClientEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(DhcpClientEvent.class),
|
||||
aString("SomeState"),
|
||||
anInt(192));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" dhcp_event <",
|
||||
" duration_ms: 192",
|
||||
" if_name: \"\"",
|
||||
" state_transition: \"SomeState\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDhcpErrorEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(DhcpErrorEvent.class),
|
||||
anInt(DhcpErrorEvent.L4_NOT_UDP));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" dhcp_event <",
|
||||
" duration_ms: 0",
|
||||
" if_name: \"\"",
|
||||
" error_code: 50397184",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpManagerEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(IpManagerEvent.class),
|
||||
anInt(IpManagerEvent.PROVISIONING_OK),
|
||||
aLong(5678));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ip_provisioning_event <",
|
||||
" event_type: 1",
|
||||
" if_name: \"\"",
|
||||
" latency_ms: 5678",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpReachabilityEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(IpReachabilityEvent.class),
|
||||
anInt(IpReachabilityEvent.NUD_FAILED));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(NetworkEvent.class),
|
||||
anInt(5),
|
||||
aLong(20410));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" network_event <",
|
||||
" event_type: 5",
|
||||
" latency_ms: 20410",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidationProbeEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(ValidationProbeEvent.class),
|
||||
aLong(40730),
|
||||
anInt(ValidationProbeEvent.PROBE_HTTP),
|
||||
anInt(204));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" validation_probe_event <",
|
||||
" latency_ms: 40730",
|
||||
" probe_result: 204",
|
||||
" probe_type: 1",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApfProgramEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(ApfProgramEvent.class),
|
||||
aLong(200),
|
||||
aLong(18),
|
||||
anInt(7),
|
||||
anInt(9),
|
||||
anInt(2048),
|
||||
anInt(3));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" apf_program_event <",
|
||||
" current_ras: 9",
|
||||
" drop_multicast: true",
|
||||
" effective_lifetime: 18",
|
||||
" filtered_ras: 7",
|
||||
" has_ipv4_addr: true",
|
||||
" lifetime: 200",
|
||||
" program_length: 2048",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApfStatsSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(ApfStats.class),
|
||||
aLong(45000),
|
||||
anInt(10),
|
||||
anInt(2),
|
||||
anInt(2),
|
||||
anInt(1),
|
||||
anInt(2),
|
||||
anInt(4),
|
||||
anInt(7),
|
||||
anInt(3),
|
||||
anInt(2048));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" apf_statistics <",
|
||||
" dropped_ras: 2",
|
||||
" duration_ms: 45000",
|
||||
" matching_ras: 2",
|
||||
" max_program_size: 2048",
|
||||
" parse_errors: 2",
|
||||
" program_updates: 4",
|
||||
" program_updates_all: 7",
|
||||
" program_updates_allowing_multicast: 3",
|
||||
" received_ras: 10",
|
||||
" total_packet_dropped: 0",
|
||||
" total_packet_processed: 0",
|
||||
" zero_lifetime_ras: 1",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRaEventSerialization() {
|
||||
ConnectivityMetricsEvent ev = describeIpEvent(
|
||||
aType(RaEvent.class),
|
||||
aLong(2000),
|
||||
aLong(400),
|
||||
aLong(300),
|
||||
aLong(-1),
|
||||
aLong(1000),
|
||||
aLong(-1));
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 0",
|
||||
" network_id: 0",
|
||||
" time_ms: 1",
|
||||
" transports: 0",
|
||||
" ra_event <",
|
||||
" dnssl_lifetime: -1",
|
||||
" prefix_preferred_lifetime: 300",
|
||||
" prefix_valid_lifetime: 400",
|
||||
" rdnss_lifetime: 1000",
|
||||
" route_info_lifetime: -1",
|
||||
" router_lifetime: 2000",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, ev);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWakeupStatsSerialization() {
|
||||
WakeupStats stats = new WakeupStats("wlan0");
|
||||
stats.totalWakeups = 14;
|
||||
stats.applicationWakeups = 5;
|
||||
stats.nonApplicationWakeups = 1;
|
||||
stats.rootWakeups = 2;
|
||||
stats.systemWakeups = 3;
|
||||
stats.noUidWakeups = 3;
|
||||
stats.l2UnicastCount = 5;
|
||||
stats.l2MulticastCount = 1;
|
||||
stats.l2BroadcastCount = 2;
|
||||
stats.ethertypes.put(0x800, 3);
|
||||
stats.ethertypes.put(0x86dd, 3);
|
||||
stats.ipNextHeaders.put(6, 5);
|
||||
|
||||
|
||||
IpConnectivityEvent got = IpConnectivityEventBuilder.toProto(stats);
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" wakeup_stats <",
|
||||
" application_wakeups: 5",
|
||||
" duration_sec: 0",
|
||||
" ethertype_counts <",
|
||||
" key: 2048",
|
||||
" value: 3",
|
||||
" >",
|
||||
" ethertype_counts <",
|
||||
" key: 34525",
|
||||
" value: 3",
|
||||
" >",
|
||||
" ip_next_header_counts <",
|
||||
" key: 6",
|
||||
" value: 5",
|
||||
" >",
|
||||
" l2_broadcast_count: 2",
|
||||
" l2_multicast_count: 1",
|
||||
" l2_unicast_count: 5",
|
||||
" no_uid_wakeups: 3",
|
||||
" non_application_wakeups: 1",
|
||||
" root_wakeups: 2",
|
||||
" system_wakeups: 3",
|
||||
" total_wakeups: 14",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, got);
|
||||
}
|
||||
|
||||
static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
|
||||
List<IpConnectivityEvent> protoInput =
|
||||
IpConnectivityEventBuilder.toProto(Arrays.asList(input));
|
||||
verifySerialization(want, protoInput.toArray(new IpConnectivityEvent[0]));
|
||||
}
|
||||
|
||||
static void verifySerialization(String want, IpConnectivityEvent... input) {
|
||||
try {
|
||||
byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input));
|
||||
IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
|
||||
assertEquals(want, log.toString());
|
||||
} catch (Exception e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
/*
|
||||
* Copyright (C) 2016, 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.server.connectivity;
|
||||
|
||||
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
|
||||
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityMetricsEvent;
|
||||
import android.net.IIpConnectivityMetrics;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.metrics.ApfProgramEvent;
|
||||
import android.net.metrics.ApfStats;
|
||||
import android.net.metrics.DhcpClientEvent;
|
||||
import android.net.metrics.IpConnectivityLog;
|
||||
import android.net.metrics.IpManagerEvent;
|
||||
import android.net.metrics.IpReachabilityEvent;
|
||||
import android.net.metrics.RaEvent;
|
||||
import android.net.metrics.ValidationProbeEvent;
|
||||
import android.os.Parcelable;
|
||||
import android.system.OsConstants;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.util.BitUtils;
|
||||
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class IpConnectivityMetricsTest {
|
||||
static final IpReachabilityEvent FAKE_EV =
|
||||
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
|
||||
|
||||
private static final String EXAMPLE_IPV4 = "192.0.2.1";
|
||||
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
|
||||
|
||||
private static final byte[] MAC_ADDR =
|
||||
{(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
|
||||
|
||||
@Mock Context mCtx;
|
||||
@Mock IIpConnectivityMetrics mMockService;
|
||||
@Mock ConnectivityManager mCm;
|
||||
|
||||
IpConnectivityMetrics mService;
|
||||
NetdEventListenerService mNetdListener;
|
||||
private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.build();
|
||||
private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
|
||||
mNetdListener = new NetdEventListenerService(mCm);
|
||||
mService.mNetdListener = mNetdListener;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBufferFlushing() {
|
||||
String output1 = getdump("flush");
|
||||
assertEquals("", output1);
|
||||
|
||||
new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
|
||||
String output2 = getdump("flush");
|
||||
assertFalse("".equals(output2));
|
||||
|
||||
String output3 = getdump("flush");
|
||||
assertEquals("", output3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRateLimiting() {
|
||||
final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
|
||||
final ApfProgramEvent ev = new ApfProgramEvent.Builder().build();
|
||||
final long fakeTimestamp = 1;
|
||||
|
||||
int attempt = 100; // More than burst quota, but less than buffer size.
|
||||
for (int i = 0; i < attempt; i++) {
|
||||
logger.log(ev);
|
||||
}
|
||||
|
||||
String output1 = getdump("flush");
|
||||
assertFalse("".equals(output1));
|
||||
|
||||
for (int i = 0; i < attempt; i++) {
|
||||
assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
|
||||
}
|
||||
|
||||
String output2 = getdump("flush");
|
||||
assertEquals("", output2);
|
||||
}
|
||||
|
||||
private void logDefaultNetworkEvent(long timeMs, NetworkAgentInfo nai,
|
||||
NetworkAgentInfo oldNai) {
|
||||
final Network network = (nai != null) ? nai.network() : null;
|
||||
final int score = (nai != null) ? nai.getCurrentScore() : 0;
|
||||
final boolean validated = (nai != null) ? nai.lastValidated : false;
|
||||
final LinkProperties lp = (nai != null) ? nai.linkProperties : null;
|
||||
final NetworkCapabilities nc = (nai != null) ? nai.networkCapabilities : null;
|
||||
|
||||
final Network prevNetwork = (oldNai != null) ? oldNai.network() : null;
|
||||
final int prevScore = (oldNai != null) ? oldNai.getCurrentScore() : 0;
|
||||
final LinkProperties prevLp = (oldNai != null) ? oldNai.linkProperties : null;
|
||||
final NetworkCapabilities prevNc = (oldNai != null) ? oldNai.networkCapabilities : null;
|
||||
|
||||
mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, network, score, validated,
|
||||
lp, nc, prevNetwork, prevScore, prevLp, prevNc);
|
||||
}
|
||||
@Test
|
||||
public void testDefaultNetworkEvents() throws Exception {
|
||||
final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
|
||||
final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
|
||||
|
||||
NetworkAgentInfo[][] defaultNetworks = {
|
||||
// nothing -> cell
|
||||
{null, makeNai(100, 10, false, true, cell)},
|
||||
// cell -> wifi
|
||||
{makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)},
|
||||
// wifi -> nothing
|
||||
{makeNai(101, 60, true, false, wifi), null},
|
||||
// nothing -> cell
|
||||
{null, makeNai(102, 10, true, true, cell)},
|
||||
// cell -> wifi
|
||||
{makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
|
||||
};
|
||||
|
||||
long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
|
||||
long durationMs = 1001;
|
||||
for (NetworkAgentInfo[] pair : defaultNetworks) {
|
||||
timeMs += durationMs;
|
||||
durationMs += durationMs;
|
||||
logDefaultNetworkEvent(timeMs, pair[1], pair[0]);
|
||||
}
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 5",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 1001",
|
||||
" final_score: 0",
|
||||
" initial_score: 0",
|
||||
" ip_support: 0",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 0",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 0",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 2002",
|
||||
" final_score: 50",
|
||||
" initial_score: 10",
|
||||
" ip_support: 3",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 0",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 2002",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 4004",
|
||||
" final_score: 60",
|
||||
" initial_score: 20",
|
||||
" ip_support: 1",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 2",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 4004",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 5",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 8008",
|
||||
" final_score: 0",
|
||||
" initial_score: 0",
|
||||
" ip_support: 0",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 4",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 0",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 102",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 16016",
|
||||
" final_score: 50",
|
||||
" initial_score: 10",
|
||||
" ip_support: 3",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 4",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 16016",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, getdump("flush"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndToEndLogging() throws Exception {
|
||||
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
|
||||
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
|
||||
|
||||
ApfStats apfStats = new ApfStats.Builder()
|
||||
.setDurationMs(45000)
|
||||
.setReceivedRas(10)
|
||||
.setMatchingRas(2)
|
||||
.setDroppedRas(2)
|
||||
.setParseErrors(2)
|
||||
.setZeroLifetimeRas(1)
|
||||
.setProgramUpdates(4)
|
||||
.setProgramUpdatesAll(7)
|
||||
.setProgramUpdatesAllowingMulticast(3)
|
||||
.setMaxProgramSize(2048)
|
||||
.build();
|
||||
|
||||
final ValidationProbeEvent validationEv = new ValidationProbeEvent.Builder()
|
||||
.setDurationMs(40730)
|
||||
.setProbeType(ValidationProbeEvent.PROBE_HTTP, true)
|
||||
.setReturnCode(204)
|
||||
.build();
|
||||
|
||||
final DhcpClientEvent event = new DhcpClientEvent.Builder()
|
||||
.setMsg("SomeState")
|
||||
.setDurationMs(192)
|
||||
.build();
|
||||
Parcelable[] events = {
|
||||
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED), event,
|
||||
new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
|
||||
validationEv,
|
||||
apfStats,
|
||||
new RaEvent(2000, 400, 300, -1, 1000, -1)
|
||||
};
|
||||
|
||||
for (int i = 0; i < events.length; i++) {
|
||||
ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
|
||||
ev.timestamp = 100 * (i + 1);
|
||||
ev.ifname = "wlan0";
|
||||
ev.data = events[i];
|
||||
logger.log(ev);
|
||||
}
|
||||
|
||||
// netId, errno, latency, destination
|
||||
connectEvent(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4);
|
||||
connectEvent(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6);
|
||||
connectEvent(100, 0, 110, EXAMPLE_IPV4);
|
||||
connectEvent(101, 0, 23, EXAMPLE_IPV4);
|
||||
connectEvent(101, 0, 45, EXAMPLE_IPV6);
|
||||
connectEvent(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4);
|
||||
|
||||
// netId, type, return code, latency
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
|
||||
dnsEvent(100, EVENT_GETHOSTBYNAME, 0, 638);
|
||||
dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
|
||||
dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
|
||||
|
||||
// iface, uid
|
||||
final byte[] mac = {0x48, 0x7c, 0x2b, 0x6a, 0x3e, 0x4b};
|
||||
final String srcIp = "192.168.2.1";
|
||||
final String dstIp = "192.168.2.23";
|
||||
final int sport = 2356;
|
||||
final int dport = 13489;
|
||||
final long now = 1001L;
|
||||
final int v4 = 0x800;
|
||||
final int tcp = 6;
|
||||
final int udp = 17;
|
||||
wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
|
||||
wakeupEvent("wlan0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
|
||||
wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
|
||||
wakeupEvent("wlan0", 10008, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
|
||||
wakeupEvent("wlan0", -1, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
|
||||
wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
|
||||
|
||||
long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
|
||||
final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
|
||||
final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
|
||||
NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell);
|
||||
NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi);
|
||||
logDefaultNetworkEvent(timeMs + 200L, cellNai, null);
|
||||
logDefaultNetworkEvent(timeMs + 300L, wifiNai, cellNai);
|
||||
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 100",
|
||||
" transports: 0",
|
||||
" ip_reachability_event <",
|
||||
" event_type: 512",
|
||||
" if_name: \"\"",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 200",
|
||||
" transports: 0",
|
||||
" dhcp_event <",
|
||||
" duration_ms: 192",
|
||||
" if_name: \"\"",
|
||||
" state_transition: \"SomeState\"",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 300",
|
||||
" transports: 0",
|
||||
" ip_provisioning_event <",
|
||||
" event_type: 1",
|
||||
" if_name: \"\"",
|
||||
" latency_ms: 5678",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 400",
|
||||
" transports: 0",
|
||||
" validation_probe_event <",
|
||||
" latency_ms: 40730",
|
||||
" probe_result: 204",
|
||||
" probe_type: 257",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 500",
|
||||
" transports: 0",
|
||||
" apf_statistics <",
|
||||
" dropped_ras: 2",
|
||||
" duration_ms: 45000",
|
||||
" matching_ras: 2",
|
||||
" max_program_size: 2048",
|
||||
" parse_errors: 2",
|
||||
" program_updates: 4",
|
||||
" program_updates_all: 7",
|
||||
" program_updates_allowing_multicast: 3",
|
||||
" received_ras: 10",
|
||||
" total_packet_dropped: 0",
|
||||
" total_packet_processed: 0",
|
||||
" zero_lifetime_ras: 1",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 600",
|
||||
" transports: 0",
|
||||
" ra_event <",
|
||||
" dnssl_lifetime: -1",
|
||||
" prefix_preferred_lifetime: 300",
|
||||
" prefix_valid_lifetime: 400",
|
||||
" rdnss_lifetime: 1000",
|
||||
" route_info_lifetime: -1",
|
||||
" router_lifetime: 2000",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 5",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 200",
|
||||
" final_score: 0",
|
||||
" initial_score: 0",
|
||||
" ip_support: 0",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 0",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 0",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" default_network_event <",
|
||||
" default_network_duration_ms: 100",
|
||||
" final_score: 50",
|
||||
" initial_score: 50",
|
||||
" ip_support: 2",
|
||||
" no_default_network_duration_ms: 0",
|
||||
" previous_default_network_link_layer: 0",
|
||||
" previous_network_ip_support: 0",
|
||||
" validation_duration_ms: 100",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 1",
|
||||
" connect_count: 3",
|
||||
" errnos_counters <",
|
||||
" key: 11",
|
||||
" value: 1",
|
||||
" >",
|
||||
" ipv6_addr_count: 1",
|
||||
" latencies_ms: 110",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 2",
|
||||
" connect_count: 2",
|
||||
" ipv6_addr_count: 1",
|
||||
" latencies_ms: 23",
|
||||
" latencies_ms: 45",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" dns_lookup_batch <",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" event_types: 2",
|
||||
" getaddrinfo_error_count: 0",
|
||||
" getaddrinfo_query_count: 0",
|
||||
" gethostbyname_error_count: 0",
|
||||
" gethostbyname_query_count: 0",
|
||||
" latencies_ms: 3456",
|
||||
" latencies_ms: 45",
|
||||
" latencies_ms: 638",
|
||||
" return_codes: 0",
|
||||
" return_codes: 3",
|
||||
" return_codes: 0",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" dns_lookup_batch <",
|
||||
" event_types: 1",
|
||||
" event_types: 2",
|
||||
" getaddrinfo_error_count: 0",
|
||||
" getaddrinfo_query_count: 0",
|
||||
" gethostbyname_error_count: 0",
|
||||
" gethostbyname_query_count: 0",
|
||||
" latencies_ms: 56",
|
||||
" latencies_ms: 34",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" wakeup_stats <",
|
||||
" application_wakeups: 3",
|
||||
" duration_sec: 0",
|
||||
" ethertype_counts <",
|
||||
" key: 2048",
|
||||
" value: 6",
|
||||
" >",
|
||||
" ip_next_header_counts <",
|
||||
" key: 6",
|
||||
" value: 3",
|
||||
" >",
|
||||
" ip_next_header_counts <",
|
||||
" key: 17",
|
||||
" value: 3",
|
||||
" >",
|
||||
" l2_broadcast_count: 0",
|
||||
" l2_multicast_count: 0",
|
||||
" l2_unicast_count: 6",
|
||||
" no_uid_wakeups: 1",
|
||||
" non_application_wakeups: 0",
|
||||
" root_wakeups: 0",
|
||||
" system_wakeups: 2",
|
||||
" total_wakeups: 6",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
|
||||
verifySerialization(want, getdump("flush"));
|
||||
}
|
||||
|
||||
String getdump(String ... command) {
|
||||
StringWriter buffer = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(buffer);
|
||||
mService.impl.dump(null, writer, command);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private void setCapabilities(int netId) {
|
||||
final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
|
||||
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
|
||||
verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
|
||||
networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
|
||||
netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
|
||||
}
|
||||
|
||||
void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
|
||||
setCapabilities(netId);
|
||||
mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
|
||||
}
|
||||
|
||||
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
|
||||
setCapabilities(netId);
|
||||
mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
|
||||
}
|
||||
|
||||
void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
|
||||
String dstIp, int sport, int dport, long now) throws Exception {
|
||||
String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
|
||||
mNetdListener.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
|
||||
}
|
||||
|
||||
NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) {
|
||||
NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
|
||||
when(nai.network()).thenReturn(new Network(netId));
|
||||
when(nai.getCurrentScore()).thenReturn(score);
|
||||
nai.linkProperties = new LinkProperties();
|
||||
nai.networkCapabilities = new NetworkCapabilities();
|
||||
nai.lastValidated = true;
|
||||
for (int t : BitUtils.unpackBits(transports)) {
|
||||
nai.networkCapabilities.addTransportType(t);
|
||||
}
|
||||
if (ipv4) {
|
||||
nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24"));
|
||||
nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
|
||||
}
|
||||
if (ipv6) {
|
||||
nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64"));
|
||||
nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0")));
|
||||
}
|
||||
return nai;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void verifySerialization(String want, String output) {
|
||||
try {
|
||||
byte[] got = Base64.decode(output, Base64.DEFAULT);
|
||||
IpConnectivityLogClass.IpConnectivityLog log =
|
||||
IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
|
||||
assertEquals(want, log.toString());
|
||||
} catch (Exception e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright (C) 2016, 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.server.connectivity;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyBoolean;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.eq;
|
||||
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.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityResources;
|
||||
import android.net.IDnsResolver;
|
||||
import android.net.INetd;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkScore;
|
||||
import android.os.Binder;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.server.ConnectivityService;
|
||||
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class LingerMonitorTest {
|
||||
static final String CELLULAR = "CELLULAR";
|
||||
static final String WIFI = "WIFI";
|
||||
|
||||
static final long LOW_RATE_LIMIT = DateUtils.MINUTE_IN_MILLIS;
|
||||
static final long HIGH_RATE_LIMIT = 0;
|
||||
|
||||
static final int LOW_DAILY_LIMIT = 2;
|
||||
static final int HIGH_DAILY_LIMIT = 1000;
|
||||
|
||||
LingerMonitor mMonitor;
|
||||
|
||||
@Mock ConnectivityService mConnService;
|
||||
@Mock IDnsResolver mDnsResolver;
|
||||
@Mock INetd mNetd;
|
||||
@Mock Context mCtx;
|
||||
@Mock NetworkNotificationManager mNotifier;
|
||||
@Mock Resources mResources;
|
||||
@Mock QosCallbackTracker mQosCallbackTracker;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mCtx.getResources()).thenReturn(mResources);
|
||||
when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
|
||||
ConnectivityResources.setResourcesContextForTest(mCtx);
|
||||
|
||||
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ConnectivityResources.setResourcesContextForTest(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransitions() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
NetworkAgentInfo nai1 = wifiNai(100);
|
||||
NetworkAgentInfo nai2 = cellNai(101);
|
||||
|
||||
assertTrue(mMonitor.isNotificationEnabled(nai1, nai2));
|
||||
assertFalse(mMonitor.isNotificationEnabled(nai2, nai1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationOnLinger() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNotification(from, to);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToastOnLinger() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyToast(from, to);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationClearedAfterDisconnect() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNotification(from, to);
|
||||
|
||||
mMonitor.noteDisconnect(to);
|
||||
verify(mNotifier, times(1)).clearNotification(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationClearedAfterSwitchingBack() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNotification(from, to);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(to, from);
|
||||
verify(mNotifier, times(1)).clearNotification(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUniqueToast() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyToast(from, to);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(to, from);
|
||||
verify(mNotifier, times(1)).clearNotification(100);
|
||||
|
||||
reset(mNotifier);
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleNotifications() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
|
||||
NetworkAgentInfo wifi1 = wifiNai(100);
|
||||
NetworkAgentInfo wifi2 = wifiNai(101);
|
||||
NetworkAgentInfo cell = cellNai(102);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(wifi1, cell);
|
||||
verifyNotification(wifi1, cell);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(cell, wifi2);
|
||||
verify(mNotifier, times(1)).clearNotification(100);
|
||||
|
||||
reset(mNotifier);
|
||||
mMonitor.noteLingerDefaultNetwork(wifi2, cell);
|
||||
verifyNotification(wifi2, cell);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRateLimiting() throws InterruptedException {
|
||||
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, LOW_RATE_LIMIT);
|
||||
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
|
||||
NetworkAgentInfo wifi1 = wifiNai(100);
|
||||
NetworkAgentInfo wifi2 = wifiNai(101);
|
||||
NetworkAgentInfo wifi3 = wifiNai(102);
|
||||
NetworkAgentInfo cell = cellNai(103);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(wifi1, cell);
|
||||
verifyNotification(wifi1, cell);
|
||||
reset(mNotifier);
|
||||
|
||||
Thread.sleep(50);
|
||||
mMonitor.noteLingerDefaultNetwork(cell, wifi2);
|
||||
mMonitor.noteLingerDefaultNetwork(wifi2, cell);
|
||||
verifyNoNotifications();
|
||||
|
||||
Thread.sleep(50);
|
||||
mMonitor.noteLingerDefaultNetwork(cell, wifi3);
|
||||
mMonitor.noteLingerDefaultNetwork(wifi3, cell);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDailyLimiting() throws InterruptedException {
|
||||
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, LOW_DAILY_LIMIT, HIGH_RATE_LIMIT);
|
||||
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
|
||||
NetworkAgentInfo wifi1 = wifiNai(100);
|
||||
NetworkAgentInfo wifi2 = wifiNai(101);
|
||||
NetworkAgentInfo wifi3 = wifiNai(102);
|
||||
NetworkAgentInfo cell = cellNai(103);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(wifi1, cell);
|
||||
verifyNotification(wifi1, cell);
|
||||
reset(mNotifier);
|
||||
|
||||
Thread.sleep(50);
|
||||
mMonitor.noteLingerDefaultNetwork(cell, wifi2);
|
||||
mMonitor.noteLingerDefaultNetwork(wifi2, cell);
|
||||
verifyNotification(wifi2, cell);
|
||||
reset(mNotifier);
|
||||
|
||||
Thread.sleep(50);
|
||||
mMonitor.noteLingerDefaultNetwork(cell, wifi3);
|
||||
mMonitor.noteLingerDefaultNetwork(wifi3, cell);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUniqueNotification() {
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNotification(from, to);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(to, from);
|
||||
verify(mNotifier, times(1)).clearNotification(100);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNotification(from, to);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreNeverValidatedNetworks() {
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
from.everValidated = false;
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreCurrentlyValidatedNetworks() {
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
from.lastValidated = true;
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNotificationType() {
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
|
||||
setNotificationSwitch();
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoTransitionToNotify() {
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_NONE);
|
||||
setNotificationSwitch(transition(WIFI, CELLULAR));
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentTransitionToNotify() {
|
||||
setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
|
||||
setNotificationSwitch(transition(CELLULAR, WIFI));
|
||||
NetworkAgentInfo from = wifiNai(100);
|
||||
NetworkAgentInfo to = cellNai(101);
|
||||
|
||||
mMonitor.noteLingerDefaultNetwork(from, to);
|
||||
verifyNoNotifications();
|
||||
}
|
||||
|
||||
void setNotificationSwitch(String... transitions) {
|
||||
when(mResources.getStringArray(R.array.config_networkNotifySwitches))
|
||||
.thenReturn(transitions);
|
||||
}
|
||||
|
||||
String transition(String from, String to) {
|
||||
return from + "-" + to;
|
||||
}
|
||||
|
||||
void setNotificationType(int type) {
|
||||
when(mResources.getInteger(R.integer.config_networkNotifySwitchType)).thenReturn(type);
|
||||
}
|
||||
|
||||
void verifyNoToast() {
|
||||
verify(mNotifier, never()).showToast(any(), any());
|
||||
}
|
||||
|
||||
void verifyNoNotification() {
|
||||
verify(mNotifier, never())
|
||||
.showNotification(anyInt(), any(), any(), any(), any(), anyBoolean());
|
||||
}
|
||||
|
||||
void verifyNoNotifications() {
|
||||
verifyNoToast();
|
||||
verifyNoNotification();
|
||||
}
|
||||
|
||||
void verifyToast(NetworkAgentInfo from, NetworkAgentInfo to) {
|
||||
verifyNoNotification();
|
||||
verify(mNotifier, times(1)).showToast(from, to);
|
||||
}
|
||||
|
||||
void verifyNotification(NetworkAgentInfo from, NetworkAgentInfo to) {
|
||||
verifyNoToast();
|
||||
verify(mNotifier, times(1)).showNotification(eq(from.network.netId),
|
||||
eq(NotificationType.NETWORK_SWITCH), eq(from), eq(to), any(), eq(true));
|
||||
}
|
||||
|
||||
NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName) {
|
||||
NetworkInfo info = new NetworkInfo(networkType, 0, networkTypeName, "");
|
||||
NetworkCapabilities caps = new NetworkCapabilities();
|
||||
caps.addCapability(0);
|
||||
caps.addTransportType(transport);
|
||||
NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info,
|
||||
new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(),
|
||||
mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd,
|
||||
mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(),
|
||||
mQosCallbackTracker, new ConnectivityService.Dependencies());
|
||||
nai.everValidated = true;
|
||||
return nai;
|
||||
}
|
||||
|
||||
NetworkAgentInfo wifiNai(int netId) {
|
||||
return nai(netId, NetworkCapabilities.TRANSPORT_WIFI,
|
||||
ConnectivityManager.TYPE_WIFI, WIFI);
|
||||
}
|
||||
|
||||
NetworkAgentInfo cellNai(int netId) {
|
||||
return nai(netId, NetworkCapabilities.TRANSPORT_CELLULAR,
|
||||
ConnectivityManager.TYPE_MOBILE, CELLULAR);
|
||||
}
|
||||
|
||||
public static class TestableLingerMonitor extends LingerMonitor {
|
||||
public TestableLingerMonitor(Context c, NetworkNotificationManager n, int l, long r) {
|
||||
super(c, n, l, r);
|
||||
}
|
||||
@Override protected PendingIntent createNotificationIntent() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.server.connectivity;
|
||||
|
||||
import android.net.ConnectivityMetricsEvent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
abstract public class MetricsTestUtil {
|
||||
private MetricsTestUtil() {
|
||||
}
|
||||
|
||||
static ConnectivityMetricsEvent ev(Parcelable p) {
|
||||
ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
|
||||
ev.timestamp = 1L;
|
||||
ev.data = p;
|
||||
return ev;
|
||||
}
|
||||
|
||||
static ConnectivityMetricsEvent describeIpEvent(Consumer<Parcel>... fs) {
|
||||
Parcel p = Parcel.obtain();
|
||||
for (Consumer<Parcel> f : fs) {
|
||||
f.accept(p);
|
||||
}
|
||||
p.setDataPosition(0);
|
||||
return ev(p.readParcelable(ClassLoader.getSystemClassLoader()));
|
||||
}
|
||||
|
||||
static Consumer<Parcel> aType(Class<?> c) {
|
||||
return aString(c.getName());
|
||||
}
|
||||
|
||||
static Consumer<Parcel> aBool(boolean b) {
|
||||
return aByte((byte) (b ? 1 : 0));
|
||||
}
|
||||
|
||||
static Consumer<Parcel> aByte(byte b) {
|
||||
return (p) -> p.writeByte(b);
|
||||
}
|
||||
|
||||
static Consumer<Parcel> anInt(int i) {
|
||||
return (p) -> p.writeInt(i);
|
||||
}
|
||||
|
||||
static Consumer<Parcel> aLong(long l) {
|
||||
return (p) -> p.writeLong(l);
|
||||
}
|
||||
|
||||
static Consumer<Parcel> aString(String s) {
|
||||
return (p) -> p.writeString(s);
|
||||
}
|
||||
|
||||
static Consumer<Parcel> aByteArray(byte... ary) {
|
||||
return (p) -> p.writeByteArray(ary);
|
||||
}
|
||||
|
||||
static Consumer<Parcel> anIntArray(int... ary) {
|
||||
return (p) -> p.writeIntArray(ary);
|
||||
}
|
||||
|
||||
static byte b(int i) {
|
||||
return (byte) i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.server.connectivity;
|
||||
|
||||
import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
import static android.net.NetworkPolicy.LIMIT_DISABLED;
|
||||
import static android.net.NetworkPolicy.SNOOZE_NEVER;
|
||||
import static android.net.NetworkPolicy.WARNING_DISABLED;
|
||||
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
|
||||
|
||||
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
|
||||
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
|
||||
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.usage.NetworkStatsManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.EthernetNetworkSpecifier;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkPolicy;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.TelephonyNetworkSpecifier;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.util.DataUnit;
|
||||
import android.util.RecurrenceRule;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.net.NetworkPolicyManagerInternal;
|
||||
import com.android.server.net.NetworkStatsManagerInternal;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class MultipathPolicyTrackerTest {
|
||||
private static final Network TEST_NETWORK = new Network(123);
|
||||
private static final int POLICY_SNOOZED = -100;
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private Context mUserAllContext;
|
||||
@Mock private Resources mResources;
|
||||
@Mock private Handler mHandler;
|
||||
@Mock private MultipathPolicyTracker.Dependencies mDeps;
|
||||
@Mock private Clock mClock;
|
||||
@Mock private ConnectivityManager mCM;
|
||||
@Mock private NetworkPolicyManager mNPM;
|
||||
@Mock private NetworkStatsManager mStatsManager;
|
||||
@Mock private NetworkPolicyManagerInternal mNPMI;
|
||||
@Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal;
|
||||
@Mock private TelephonyManager mTelephonyManager;
|
||||
private MockContentResolver mContentResolver;
|
||||
|
||||
private ArgumentCaptor<BroadcastReceiver> mConfigChangeReceiverCaptor;
|
||||
|
||||
private MultipathPolicyTracker mTracker;
|
||||
|
||||
private Clock mPreviousRecurrenceRuleClock;
|
||||
private boolean mRecurrenceRuleClockMocked;
|
||||
|
||||
private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
|
||||
when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
|
||||
when(mContext.getSystemService(serviceName)).thenReturn(service);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mPreviousRecurrenceRuleClock = RecurrenceRule.sClock;
|
||||
RecurrenceRule.sClock = mClock;
|
||||
mRecurrenceRuleClockMocked = true;
|
||||
|
||||
mConfigChangeReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
|
||||
when(mContext.getResources()).thenReturn(mResources);
|
||||
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
|
||||
// Mock user id to all users that Context#registerReceiver will register with all users too.
|
||||
doReturn(UserHandle.ALL.getIdentifier()).when(mUserAllContext).getUserId();
|
||||
when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt()))
|
||||
.thenReturn(mUserAllContext);
|
||||
when(mUserAllContext.registerReceiver(mConfigChangeReceiverCaptor.capture(),
|
||||
argThat(f -> f.hasAction(ACTION_CONFIGURATION_CHANGED)), any(), any()))
|
||||
.thenReturn(null);
|
||||
|
||||
when(mDeps.getClock()).thenReturn(mClock);
|
||||
|
||||
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
|
||||
|
||||
mContentResolver = Mockito.spy(new MockContentResolver(mContext));
|
||||
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
|
||||
Settings.Global.clearProviderForTest();
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
|
||||
mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, mCM);
|
||||
mockService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class, mNPM);
|
||||
mockService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, mStatsManager);
|
||||
mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager);
|
||||
|
||||
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
|
||||
LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI);
|
||||
|
||||
LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
|
||||
LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal);
|
||||
|
||||
mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Avoid setting static clock to null (which should normally not be the case)
|
||||
// if MockitoAnnotations.initMocks threw an exception
|
||||
if (mRecurrenceRuleClockMocked) {
|
||||
RecurrenceRule.sClock = mPreviousRecurrenceRuleClock;
|
||||
}
|
||||
mRecurrenceRuleClockMocked = false;
|
||||
}
|
||||
|
||||
private void setDefaultQuotaGlobalSetting(long setting) {
|
||||
Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
|
||||
(int) setting);
|
||||
}
|
||||
|
||||
private void testGetMultipathPreference(
|
||||
long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit,
|
||||
long defaultGlobalSetting, long defaultResSetting, boolean roaming) {
|
||||
|
||||
// TODO: tests should not use ZoneId.systemDefault() once code handles TZ correctly.
|
||||
final ZonedDateTime now = ZonedDateTime.ofInstant(
|
||||
Instant.parse("2017-04-02T10:11:12Z"), ZoneId.systemDefault());
|
||||
final ZonedDateTime startOfDay = now.truncatedTo(ChronoUnit.DAYS);
|
||||
when(mClock.millis()).thenReturn(now.toInstant().toEpochMilli());
|
||||
when(mClock.instant()).thenReturn(now.toInstant());
|
||||
when(mClock.getZone()).thenReturn(ZoneId.systemDefault());
|
||||
|
||||
// Setup plan quota
|
||||
when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
|
||||
.thenReturn(subscriptionQuota);
|
||||
|
||||
// Setup user policy warning / limit
|
||||
if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) {
|
||||
final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z");
|
||||
final RecurrenceRule recurrenceRule = new RecurrenceRule(
|
||||
ZonedDateTime.ofInstant(
|
||||
recurrenceStart,
|
||||
ZoneId.systemDefault()),
|
||||
null /* end */,
|
||||
Period.ofMonths(1));
|
||||
final boolean snoozeWarning = policyWarning == POLICY_SNOOZED;
|
||||
final boolean snoozeLimit = policyLimit == POLICY_SNOOZED;
|
||||
when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] {
|
||||
new NetworkPolicy(
|
||||
NetworkTemplate.buildTemplateMobileWildcard(),
|
||||
recurrenceRule,
|
||||
snoozeWarning ? 0 : policyWarning,
|
||||
snoozeLimit ? 0 : policyLimit,
|
||||
snoozeWarning ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
|
||||
snoozeLimit ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
|
||||
SNOOZE_NEVER,
|
||||
true /* metered */,
|
||||
false /* inferred */)
|
||||
});
|
||||
} else {
|
||||
when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]);
|
||||
}
|
||||
|
||||
// Setup default quota in settings and resources
|
||||
if (defaultGlobalSetting > 0) {
|
||||
setDefaultQuotaGlobalSetting(defaultGlobalSetting);
|
||||
}
|
||||
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
|
||||
.thenReturn((int) defaultResSetting);
|
||||
|
||||
when(mNetworkStatsManagerInternal.getNetworkTotalBytes(
|
||||
any(),
|
||||
eq(startOfDay.toInstant().toEpochMilli()),
|
||||
eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday);
|
||||
|
||||
ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
|
||||
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
|
||||
mTracker.start();
|
||||
verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
|
||||
|
||||
// Simulate callback after capability changes
|
||||
NetworkCapabilities capabilities = new NetworkCapabilities()
|
||||
.addCapability(NET_CAPABILITY_INTERNET)
|
||||
.addTransportType(TRANSPORT_CELLULAR)
|
||||
.setNetworkSpecifier(new EthernetNetworkSpecifier("eth234"));
|
||||
if (!roaming) {
|
||||
capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
|
||||
}
|
||||
networkCallback.getValue().onCapabilitiesChanged(
|
||||
TEST_NETWORK,
|
||||
capabilities);
|
||||
|
||||
// make sure it also works with the new introduced TelephonyNetworkSpecifier
|
||||
capabilities = new NetworkCapabilities()
|
||||
.addCapability(NET_CAPABILITY_INTERNET)
|
||||
.addTransportType(TRANSPORT_CELLULAR)
|
||||
.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
|
||||
.setSubscriptionId(234).build());
|
||||
if (!roaming) {
|
||||
capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
|
||||
}
|
||||
networkCallback.getValue().onCapabilitiesChanged(
|
||||
TEST_NETWORK,
|
||||
capabilities);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMultipathPreference_SubscriptionQuota() {
|
||||
testGetMultipathPreference(
|
||||
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
|
||||
DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
|
||||
DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
|
||||
LIMIT_DISABLED,
|
||||
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
|
||||
2_500_000 /* defaultResSetting */,
|
||||
false /* roaming */);
|
||||
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMultipathPreference_UserWarningQuota() {
|
||||
testGetMultipathPreference(
|
||||
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
|
||||
OPPORTUNISTIC_QUOTA_UNKNOWN,
|
||||
// 29 days from Apr. 2nd to May 1st
|
||||
DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */,
|
||||
LIMIT_DISABLED,
|
||||
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
|
||||
2_500_000 /* defaultResSetting */,
|
||||
false /* roaming */);
|
||||
|
||||
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMultipathPreference_SnoozedWarningQuota() {
|
||||
testGetMultipathPreference(
|
||||
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
|
||||
OPPORTUNISTIC_QUOTA_UNKNOWN,
|
||||
// 29 days from Apr. 2nd to May 1st
|
||||
POLICY_SNOOZED /* policyWarning */,
|
||||
DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */,
|
||||
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
|
||||
2_500_000 /* defaultResSetting */,
|
||||
false /* roaming */);
|
||||
|
||||
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMultipathPreference_SnoozedBothQuota() {
|
||||
testGetMultipathPreference(
|
||||
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
|
||||
OPPORTUNISTIC_QUOTA_UNKNOWN,
|
||||
// 29 days from Apr. 2nd to May 1st
|
||||
POLICY_SNOOZED /* policyWarning */,
|
||||
POLICY_SNOOZED /* policyLimit */,
|
||||
DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
|
||||
2_500_000 /* defaultResSetting */,
|
||||
false /* roaming */);
|
||||
|
||||
// Default global setting should be used: 12 - 7 = 5
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMultipathPreference_SettingChanged() {
|
||||
testGetMultipathPreference(
|
||||
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
|
||||
OPPORTUNISTIC_QUOTA_UNKNOWN,
|
||||
WARNING_DISABLED,
|
||||
LIMIT_DISABLED,
|
||||
-1 /* defaultGlobalSetting */,
|
||||
DataUnit.MEGABYTES.toBytes(10) /* defaultResSetting */,
|
||||
false /* roaming */);
|
||||
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
|
||||
|
||||
// Update setting
|
||||
setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14));
|
||||
mTracker.mSettingsObserver.onChange(
|
||||
false, Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES));
|
||||
|
||||
// Callback must have been re-registered with new setting
|
||||
verify(mStatsManager, times(1)).unregisterUsageCallback(any());
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMultipathPreference_ResourceChanged() {
|
||||
testGetMultipathPreference(
|
||||
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
|
||||
OPPORTUNISTIC_QUOTA_UNKNOWN,
|
||||
WARNING_DISABLED,
|
||||
LIMIT_DISABLED,
|
||||
-1 /* defaultGlobalSetting */,
|
||||
DataUnit.MEGABYTES.toBytes(14) /* defaultResSetting */,
|
||||
false /* roaming */);
|
||||
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
|
||||
|
||||
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
|
||||
.thenReturn((int) DataUnit.MEGABYTES.toBytes(16));
|
||||
|
||||
final BroadcastReceiver configChangeReceiver = mConfigChangeReceiverCaptor.getValue();
|
||||
assertNotNull(configChangeReceiver);
|
||||
configChangeReceiver.onReceive(mContext, new Intent());
|
||||
|
||||
// Uses the new setting (16 - 2 = 14MB)
|
||||
verify(mStatsManager, times(1)).registerUsageCallback(
|
||||
any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.server.connectivity;
|
||||
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.IDnsResolver;
|
||||
import android.net.INetd;
|
||||
import android.net.InterfaceConfigurationParcel;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.ConnectivityService;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class Nat464XlatTest {
|
||||
|
||||
static final String BASE_IFACE = "test0";
|
||||
static final String STACKED_IFACE = "v4-test0";
|
||||
static final LinkAddress V6ADDR = new LinkAddress("2001:db8:1::f00/64");
|
||||
static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29");
|
||||
static final String NAT64_PREFIX = "64:ff9b::/96";
|
||||
static final String OTHER_NAT64_PREFIX = "2001:db8:0:64::/96";
|
||||
static final int NETID = 42;
|
||||
|
||||
@Mock ConnectivityService mConnectivity;
|
||||
@Mock IDnsResolver mDnsResolver;
|
||||
@Mock INetd mNetd;
|
||||
@Mock NetworkAgentInfo mNai;
|
||||
|
||||
TestLooper mLooper;
|
||||
Handler mHandler;
|
||||
NetworkAgentConfig mAgentConfig = new NetworkAgentConfig();
|
||||
|
||||
Nat464Xlat makeNat464Xlat(boolean isCellular464XlatEnabled) {
|
||||
return new Nat464Xlat(mNai, mNetd, mDnsResolver, new ConnectivityService.Dependencies()) {
|
||||
@Override protected int getNetId() {
|
||||
return NETID;
|
||||
}
|
||||
|
||||
@Override protected boolean isCellular464XlatEnabled() {
|
||||
return isCellular464XlatEnabled;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void markNetworkConnected() {
|
||||
mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
|
||||
}
|
||||
|
||||
private void markNetworkDisconnected() {
|
||||
mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, "", "");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mLooper = new TestLooper();
|
||||
mHandler = new Handler(mLooper.getLooper());
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mNai.linkProperties = new LinkProperties();
|
||||
mNai.linkProperties.setInterfaceName(BASE_IFACE);
|
||||
mNai.networkInfo = new NetworkInfo(null);
|
||||
mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
|
||||
mNai.networkCapabilities = new NetworkCapabilities();
|
||||
markNetworkConnected();
|
||||
when(mNai.connService()).thenReturn(mConnectivity);
|
||||
when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
|
||||
when(mNai.handler()).thenReturn(mHandler);
|
||||
final InterfaceConfigurationParcel mConfig = new InterfaceConfigurationParcel();
|
||||
when(mNetd.interfaceGetCfg(eq(STACKED_IFACE))).thenReturn(mConfig);
|
||||
mConfig.ipv4Addr = ADDR.getAddress().getHostAddress();
|
||||
mConfig.prefixLength = ADDR.getPrefixLength();
|
||||
}
|
||||
|
||||
private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) {
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
|
||||
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
|
||||
nai.networkInfo.getDetailedState(),
|
||||
mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
|
||||
nai.linkProperties.getLinkAddresses());
|
||||
assertEquals(msg, expected, nat.requiresClat(nai));
|
||||
}
|
||||
|
||||
private void assertShouldStartClat(boolean expected, NetworkAgentInfo nai) {
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
|
||||
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
|
||||
nai.networkInfo.getDetailedState(),
|
||||
mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
|
||||
nai.linkProperties.getLinkAddresses());
|
||||
assertEquals(msg, expected, nat.shouldStartClat(nai));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequiresClat() throws Exception {
|
||||
final int[] supportedTypes = {
|
||||
ConnectivityManager.TYPE_MOBILE,
|
||||
ConnectivityManager.TYPE_WIFI,
|
||||
ConnectivityManager.TYPE_ETHERNET,
|
||||
};
|
||||
|
||||
// NetworkInfo doesn't allow setting the State directly, but rather
|
||||
// requires setting DetailedState in order set State as a side-effect.
|
||||
final NetworkInfo.DetailedState[] supportedDetailedStates = {
|
||||
NetworkInfo.DetailedState.CONNECTED,
|
||||
NetworkInfo.DetailedState.SUSPENDED,
|
||||
};
|
||||
|
||||
LinkProperties oldLp = new LinkProperties(mNai.linkProperties);
|
||||
for (int type : supportedTypes) {
|
||||
mNai.networkInfo.setType(type);
|
||||
for (NetworkInfo.DetailedState state : supportedDetailedStates) {
|
||||
mNai.networkInfo.setDetailedState(state, "reason", "extraInfo");
|
||||
|
||||
mNai.linkProperties.setNat64Prefix(new IpPrefix(OTHER_NAT64_PREFIX));
|
||||
assertRequiresClat(false, mNai);
|
||||
assertShouldStartClat(false, mNai);
|
||||
|
||||
mNai.linkProperties.addLinkAddress(new LinkAddress("fc00::1/64"));
|
||||
assertRequiresClat(false, mNai);
|
||||
assertShouldStartClat(false, mNai);
|
||||
|
||||
mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
|
||||
assertRequiresClat(true, mNai);
|
||||
assertShouldStartClat(true, mNai);
|
||||
|
||||
mAgentConfig.skip464xlat = true;
|
||||
assertRequiresClat(false, mNai);
|
||||
assertShouldStartClat(false, mNai);
|
||||
|
||||
mAgentConfig.skip464xlat = false;
|
||||
assertRequiresClat(true, mNai);
|
||||
assertShouldStartClat(true, mNai);
|
||||
|
||||
mNai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.2/24"));
|
||||
assertRequiresClat(false, mNai);
|
||||
assertShouldStartClat(false, mNai);
|
||||
|
||||
mNai.linkProperties.removeLinkAddress(new LinkAddress("192.0.2.2/24"));
|
||||
assertRequiresClat(true, mNai);
|
||||
assertShouldStartClat(true, mNai);
|
||||
|
||||
mNai.linkProperties.setNat64Prefix(null);
|
||||
assertRequiresClat(true, mNai);
|
||||
assertShouldStartClat(false, mNai);
|
||||
|
||||
mNai.linkProperties = new LinkProperties(oldLp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeClatUnnecessary(boolean dueToDisconnect) {
|
||||
if (dueToDisconnect) {
|
||||
markNetworkDisconnected();
|
||||
} else {
|
||||
mNai.linkProperties.addLinkAddress(ADDR);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception {
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
|
||||
|
||||
mNai.linkProperties.addLinkAddress(V6ADDR);
|
||||
|
||||
nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
|
||||
|
||||
// Start clat.
|
||||
nat.start();
|
||||
|
||||
verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
|
||||
|
||||
// Stacked interface up notification arrives.
|
||||
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
verify(mNetd).interfaceGetCfg(eq(STACKED_IFACE));
|
||||
verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
assertFalse(c.getValue().getStackedLinks().isEmpty());
|
||||
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertRunning(nat);
|
||||
|
||||
// Stop clat (Network disconnects, IPv4 addr appears, ...).
|
||||
makeClatUnnecessary(dueToDisconnect);
|
||||
nat.stop();
|
||||
|
||||
verify(mNetd).clatdStop(eq(BASE_IFACE));
|
||||
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
assertTrue(c.getValue().getStackedLinks().isEmpty());
|
||||
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
|
||||
assertIdle(nat);
|
||||
|
||||
// Stacked interface removed notification arrives and is ignored.
|
||||
nat.interfaceRemoved(STACKED_IFACE);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
verifyNoMoreInteractions(mNetd, mConnectivity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalStartAndStopDueToDisconnect() throws Exception {
|
||||
checkNormalStartAndStop(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalStartAndStopDueToIpv4Addr() throws Exception {
|
||||
checkNormalStartAndStop(false);
|
||||
}
|
||||
|
||||
private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception {
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
|
||||
InOrder inOrder = inOrder(mNetd, mConnectivity);
|
||||
|
||||
mNai.linkProperties.addLinkAddress(V6ADDR);
|
||||
|
||||
nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
|
||||
|
||||
nat.start();
|
||||
|
||||
inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
|
||||
|
||||
// Stacked interface up notification arrives.
|
||||
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
assertFalse(c.getValue().getStackedLinks().isEmpty());
|
||||
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertRunning(nat);
|
||||
|
||||
// ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
|
||||
nat.stop();
|
||||
|
||||
inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
|
||||
|
||||
inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
assertTrue(c.getValue().getStackedLinks().isEmpty());
|
||||
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertIdle(nat);
|
||||
|
||||
if (interfaceRemovedFirst) {
|
||||
// Stacked interface removed notification arrives and is ignored.
|
||||
nat.interfaceRemoved(STACKED_IFACE);
|
||||
mLooper.dispatchNext();
|
||||
nat.interfaceLinkStateChanged(STACKED_IFACE, false);
|
||||
mLooper.dispatchNext();
|
||||
}
|
||||
|
||||
assertTrue(c.getValue().getStackedLinks().isEmpty());
|
||||
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertIdle(nat);
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
|
||||
nat.start();
|
||||
|
||||
inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
|
||||
|
||||
if (!interfaceRemovedFirst) {
|
||||
// Stacked interface removed notification arrives and is ignored.
|
||||
nat.interfaceRemoved(STACKED_IFACE);
|
||||
mLooper.dispatchNext();
|
||||
nat.interfaceLinkStateChanged(STACKED_IFACE, false);
|
||||
mLooper.dispatchNext();
|
||||
}
|
||||
|
||||
// Stacked interface up notification arrives.
|
||||
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
assertFalse(c.getValue().getStackedLinks().isEmpty());
|
||||
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertRunning(nat);
|
||||
|
||||
// ConnectivityService stops clat again.
|
||||
nat.stop();
|
||||
|
||||
inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
|
||||
|
||||
inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
assertTrue(c.getValue().getStackedLinks().isEmpty());
|
||||
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertIdle(nat);
|
||||
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartStopStart() throws Exception {
|
||||
checkStartStopStart(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartStopStartBeforeInterfaceRemoved() throws Exception {
|
||||
checkStartStopStart(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClatdCrashWhileRunning() throws Exception {
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
|
||||
|
||||
nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
|
||||
|
||||
nat.start();
|
||||
|
||||
verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
|
||||
|
||||
// Stacked interface up notification arrives.
|
||||
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
verify(mNetd).interfaceGetCfg(eq(STACKED_IFACE));
|
||||
verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
assertFalse(c.getValue().getStackedLinks().isEmpty());
|
||||
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertRunning(nat);
|
||||
|
||||
// Stacked interface removed notification arrives (clatd crashed, ...).
|
||||
nat.interfaceRemoved(STACKED_IFACE);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
verify(mNetd).clatdStop(eq(BASE_IFACE));
|
||||
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
|
||||
verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
|
||||
assertTrue(c.getValue().getStackedLinks().isEmpty());
|
||||
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
|
||||
assertIdle(nat);
|
||||
|
||||
// ConnectivityService stops clat: no-op.
|
||||
nat.stop();
|
||||
|
||||
verifyNoMoreInteractions(mNetd, mConnectivity);
|
||||
}
|
||||
|
||||
private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception {
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
|
||||
mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
|
||||
|
||||
nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
|
||||
|
||||
nat.start();
|
||||
|
||||
verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
|
||||
|
||||
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
|
||||
makeClatUnnecessary(dueToDisconnect);
|
||||
nat.stop();
|
||||
|
||||
verify(mNetd).clatdStop(eq(BASE_IFACE));
|
||||
verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
|
||||
assertIdle(nat);
|
||||
|
||||
// In-flight interface up notification arrives: no-op
|
||||
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
// Interface removed notification arrives after stopClatd() takes effect: no-op.
|
||||
nat.interfaceRemoved(STACKED_IFACE);
|
||||
mLooper.dispatchNext();
|
||||
|
||||
assertIdle(nat);
|
||||
|
||||
verifyNoMoreInteractions(mNetd, mConnectivity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopDueToDisconnectBeforeClatdStarts() throws Exception {
|
||||
checkStopBeforeClatdStarts(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopDueToIpv4AddrBeforeClatdStarts() throws Exception {
|
||||
checkStopBeforeClatdStarts(false);
|
||||
}
|
||||
|
||||
private void checkStopAndClatdNeverStarts(boolean dueToDisconnect) throws Exception {
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
|
||||
mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
|
||||
|
||||
nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
|
||||
|
||||
nat.start();
|
||||
|
||||
verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
|
||||
|
||||
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
|
||||
makeClatUnnecessary(dueToDisconnect);
|
||||
nat.stop();
|
||||
|
||||
verify(mNetd).clatdStop(eq(BASE_IFACE));
|
||||
verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
|
||||
assertIdle(nat);
|
||||
|
||||
verifyNoMoreInteractions(mNetd, mConnectivity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopDueToDisconnectAndClatdNeverStarts() throws Exception {
|
||||
checkStopAndClatdNeverStarts(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopDueToIpv4AddressAndClatdNeverStarts() throws Exception {
|
||||
checkStopAndClatdNeverStarts(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNat64PrefixPreference() throws Exception {
|
||||
final IpPrefix prefixFromDns = new IpPrefix(NAT64_PREFIX);
|
||||
final IpPrefix prefixFromRa = new IpPrefix(OTHER_NAT64_PREFIX);
|
||||
|
||||
Nat464Xlat nat = makeNat464Xlat(true);
|
||||
|
||||
final LinkProperties emptyLp = new LinkProperties();
|
||||
LinkProperties fixedupLp;
|
||||
|
||||
fixedupLp = new LinkProperties();
|
||||
nat.setNat64PrefixFromDns(prefixFromDns);
|
||||
nat.fixupLinkProperties(emptyLp, fixedupLp);
|
||||
assertEquals(prefixFromDns, fixedupLp.getNat64Prefix());
|
||||
|
||||
fixedupLp = new LinkProperties();
|
||||
nat.setNat64PrefixFromRa(prefixFromRa);
|
||||
nat.fixupLinkProperties(emptyLp, fixedupLp);
|
||||
assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
|
||||
|
||||
fixedupLp = new LinkProperties();
|
||||
nat.setNat64PrefixFromRa(null);
|
||||
nat.fixupLinkProperties(emptyLp, fixedupLp);
|
||||
assertEquals(prefixFromDns, fixedupLp.getNat64Prefix());
|
||||
|
||||
fixedupLp = new LinkProperties();
|
||||
nat.setNat64PrefixFromRa(prefixFromRa);
|
||||
nat.fixupLinkProperties(emptyLp, fixedupLp);
|
||||
assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
|
||||
|
||||
fixedupLp = new LinkProperties();
|
||||
nat.setNat64PrefixFromDns(null);
|
||||
nat.fixupLinkProperties(emptyLp, fixedupLp);
|
||||
assertEquals(prefixFromRa, fixedupLp.getNat64Prefix());
|
||||
|
||||
fixedupLp = new LinkProperties();
|
||||
nat.setNat64PrefixFromRa(null);
|
||||
nat.fixupLinkProperties(emptyLp, fixedupLp);
|
||||
assertEquals(null, fixedupLp.getNat64Prefix());
|
||||
}
|
||||
|
||||
private void checkClatDisabledOnCellular(boolean onCellular) throws Exception {
|
||||
// Disable 464xlat on cellular networks.
|
||||
Nat464Xlat nat = makeNat464Xlat(false);
|
||||
mNai.linkProperties.addLinkAddress(V6ADDR);
|
||||
mNai.networkCapabilities.setTransportType(TRANSPORT_CELLULAR, onCellular);
|
||||
nat.update();
|
||||
|
||||
final IpPrefix nat64Prefix = new IpPrefix(NAT64_PREFIX);
|
||||
if (onCellular) {
|
||||
// Prefix discovery is never started.
|
||||
verify(mDnsResolver, never()).startPrefix64Discovery(eq(NETID));
|
||||
assertIdle(nat);
|
||||
|
||||
// If a NAT64 prefix comes in from an RA, clat is not started either.
|
||||
mNai.linkProperties.setNat64Prefix(nat64Prefix);
|
||||
nat.setNat64PrefixFromRa(nat64Prefix);
|
||||
nat.update();
|
||||
verify(mNetd, never()).clatdStart(anyString(), anyString());
|
||||
assertIdle(nat);
|
||||
} else {
|
||||
// Prefix discovery is started.
|
||||
verify(mDnsResolver).startPrefix64Discovery(eq(NETID));
|
||||
assertIdle(nat);
|
||||
|
||||
// If a NAT64 prefix comes in from an RA, clat is started.
|
||||
mNai.linkProperties.setNat64Prefix(nat64Prefix);
|
||||
nat.setNat64PrefixFromRa(nat64Prefix);
|
||||
nat.update();
|
||||
verify(mNetd).clatdStart(BASE_IFACE, NAT64_PREFIX);
|
||||
assertStarting(nat);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClatDisabledOnCellular() throws Exception {
|
||||
checkClatDisabledOnCellular(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClatDisabledOnNonCellular() throws Exception {
|
||||
checkClatDisabledOnCellular(false);
|
||||
}
|
||||
|
||||
static void assertIdle(Nat464Xlat nat) {
|
||||
assertTrue("Nat464Xlat was not IDLE", !nat.isStarted());
|
||||
}
|
||||
|
||||
static void assertStarting(Nat464Xlat nat) {
|
||||
assertTrue("Nat464Xlat was not STARTING", nat.isStarting());
|
||||
}
|
||||
|
||||
static void assertRunning(Nat464Xlat nat) {
|
||||
assertTrue("Nat464Xlat was not RUNNING", nat.isRunning());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright (C) 2016, 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.server.connectivity;
|
||||
|
||||
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
|
||||
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
|
||||
|
||||
import static com.android.testutils.MiscAsserts.assertStringContains;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.system.OsConstants;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
|
||||
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetdEventListenerServiceTest {
|
||||
private static final String EXAMPLE_IPV4 = "192.0.2.1";
|
||||
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
|
||||
|
||||
private static final byte[] MAC_ADDR =
|
||||
{(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
|
||||
|
||||
NetdEventListenerService mService;
|
||||
ConnectivityManager mCm;
|
||||
private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.build();
|
||||
private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mCm = mock(ConnectivityManager.class);
|
||||
mService = new NetdEventListenerService(mCm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWakeupEventLogging() throws Exception {
|
||||
final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
|
||||
final long now = System.currentTimeMillis();
|
||||
final String iface = "wlan0";
|
||||
final byte[] mac = MAC_ADDR;
|
||||
final String srcIp = "192.168.2.1";
|
||||
final String dstIp = "192.168.2.23";
|
||||
final String srcIp6 = "2001:db8:4:fd00:a585:13d1:6a23:4fb4";
|
||||
final String dstIp6 = "2001:db8:4006:807::200a";
|
||||
final int sport = 2356;
|
||||
final int dport = 13489;
|
||||
|
||||
final int v4 = 0x800;
|
||||
final int v6 = 0x86dd;
|
||||
final int tcp = 6;
|
||||
final int udp = 17;
|
||||
final int icmp6 = 58;
|
||||
|
||||
// Baseline without any event
|
||||
String[] baseline = listNetdEvent();
|
||||
|
||||
int[] uids = {10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004};
|
||||
wakeupEvent(iface, uids[0], v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent(iface, uids[1], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent(iface, uids[2], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent(iface, uids[3], v4, icmp6, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent(iface, uids[4], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent(iface, uids[5], v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent(iface, uids[6], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent(iface, uids[7], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent(iface, uids[8], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
|
||||
String[] events2 = remove(listNetdEvent(), baseline);
|
||||
int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
|
||||
assertEquals(expectedLength2, events2.length);
|
||||
assertStringContains(events2[0], "WakeupStats");
|
||||
assertStringContains(events2[0], "wlan0");
|
||||
assertStringContains(events2[0], "0x800");
|
||||
assertStringContains(events2[0], "0x86dd");
|
||||
for (int i = 0; i < uids.length; i++) {
|
||||
String got = events2[i+1];
|
||||
assertStringContains(got, "WakeupEvent");
|
||||
assertStringContains(got, "wlan0");
|
||||
assertStringContains(got, "uid: " + uids[i]);
|
||||
}
|
||||
|
||||
int uid = 20000;
|
||||
for (int i = 0; i < BUFFER_LENGTH * 2; i++) {
|
||||
long ts = now + 10;
|
||||
wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, ts);
|
||||
}
|
||||
|
||||
String[] events3 = remove(listNetdEvent(), baseline);
|
||||
int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
|
||||
assertEquals(expectedLength3, events3.length);
|
||||
assertStringContains(events2[0], "WakeupStats");
|
||||
assertStringContains(events2[0], "wlan0");
|
||||
for (int i = 1; i < expectedLength3; i++) {
|
||||
String got = events3[i];
|
||||
assertStringContains(got, "WakeupEvent");
|
||||
assertStringContains(got, "wlan0");
|
||||
assertStringContains(got, "uid: " + uid);
|
||||
}
|
||||
|
||||
uid = 45678;
|
||||
wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, now);
|
||||
|
||||
String[] events4 = remove(listNetdEvent(), baseline);
|
||||
String lastEvent = events4[events4.length - 1];
|
||||
assertStringContains(lastEvent, "WakeupEvent");
|
||||
assertStringContains(lastEvent, "wlan0");
|
||||
assertStringContains(lastEvent, "uid: " + uid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWakeupStatsLogging() throws Exception {
|
||||
final byte[] mac = MAC_ADDR;
|
||||
final String srcIp = "192.168.2.1";
|
||||
final String dstIp = "192.168.2.23";
|
||||
final String srcIp6 = "2401:fa00:4:fd00:a585:13d1:6a23:4fb4";
|
||||
final String dstIp6 = "2404:6800:4006:807::200a";
|
||||
final int sport = 2356;
|
||||
final int dport = 13489;
|
||||
final long now = 1001L;
|
||||
|
||||
final int v4 = 0x800;
|
||||
final int v6 = 0x86dd;
|
||||
final int tcp = 6;
|
||||
final int udp = 17;
|
||||
final int icmp6 = 58;
|
||||
|
||||
wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("rmnet0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("wlan0", 10004, v4, udp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("wlan0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
|
||||
wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent("rmnet0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
|
||||
wakeupEvent("wlan0", 1010, v4, udp, mac, srcIp, dstIp, sport, dport, now);
|
||||
|
||||
String got = flushStatistics();
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" wakeup_stats <",
|
||||
" application_wakeups: 3",
|
||||
" duration_sec: 0",
|
||||
" ethertype_counts <",
|
||||
" key: 2048",
|
||||
" value: 4",
|
||||
" >",
|
||||
" ethertype_counts <",
|
||||
" key: 34525",
|
||||
" value: 1",
|
||||
" >",
|
||||
" ip_next_header_counts <",
|
||||
" key: 6",
|
||||
" value: 5",
|
||||
" >",
|
||||
" l2_broadcast_count: 0",
|
||||
" l2_multicast_count: 0",
|
||||
" l2_unicast_count: 5",
|
||||
" no_uid_wakeups: 0",
|
||||
" non_application_wakeups: 0",
|
||||
" root_wakeups: 0",
|
||||
" system_wakeups: 2",
|
||||
" total_wakeups: 5",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 0",
|
||||
" time_ms: 0",
|
||||
" transports: 0",
|
||||
" wakeup_stats <",
|
||||
" application_wakeups: 2",
|
||||
" duration_sec: 0",
|
||||
" ethertype_counts <",
|
||||
" key: 2048",
|
||||
" value: 5",
|
||||
" >",
|
||||
" ethertype_counts <",
|
||||
" key: 34525",
|
||||
" value: 5",
|
||||
" >",
|
||||
" ip_next_header_counts <",
|
||||
" key: 6",
|
||||
" value: 3",
|
||||
" >",
|
||||
" ip_next_header_counts <",
|
||||
" key: 17",
|
||||
" value: 5",
|
||||
" >",
|
||||
" ip_next_header_counts <",
|
||||
" key: 58",
|
||||
" value: 2",
|
||||
" >",
|
||||
" l2_broadcast_count: 0",
|
||||
" l2_multicast_count: 0",
|
||||
" l2_unicast_count: 10",
|
||||
" no_uid_wakeups: 2",
|
||||
" non_application_wakeups: 1",
|
||||
" root_wakeups: 2",
|
||||
" system_wakeups: 3",
|
||||
" total_wakeups: 10",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
assertEquals(want, got);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDnsLogging() throws Exception {
|
||||
asyncDump(100);
|
||||
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 0, 267);
|
||||
dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230);
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 1, 2111);
|
||||
dnsEvent(100, EVENT_GETADDRINFO, 0, 450);
|
||||
dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638);
|
||||
dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300);
|
||||
dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
|
||||
dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
|
||||
dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
|
||||
dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56);
|
||||
dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
|
||||
dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
|
||||
|
||||
String got = flushStatistics();
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" dns_lookup_batch <",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" event_types: 2",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" event_types: 2",
|
||||
" event_types: 2",
|
||||
" getaddrinfo_error_count: 0",
|
||||
" getaddrinfo_query_count: 0",
|
||||
" gethostbyname_error_count: 0",
|
||||
" gethostbyname_query_count: 0",
|
||||
" latencies_ms: 3456",
|
||||
" latencies_ms: 267",
|
||||
" latencies_ms: 1230",
|
||||
" latencies_ms: 45",
|
||||
" latencies_ms: 2111",
|
||||
" latencies_ms: 450",
|
||||
" latencies_ms: 638",
|
||||
" latencies_ms: 1300",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" return_codes: 22",
|
||||
" return_codes: 3",
|
||||
" return_codes: 1",
|
||||
" return_codes: 0",
|
||||
" return_codes: 200",
|
||||
" return_codes: 178",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" dns_lookup_batch <",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" event_types: 2",
|
||||
" event_types: 1",
|
||||
" event_types: 1",
|
||||
" getaddrinfo_error_count: 0",
|
||||
" getaddrinfo_query_count: 0",
|
||||
" gethostbyname_error_count: 0",
|
||||
" gethostbyname_query_count: 0",
|
||||
" latencies_ms: 56",
|
||||
" latencies_ms: 78",
|
||||
" latencies_ms: 14",
|
||||
" latencies_ms: 56",
|
||||
" latencies_ms: 78",
|
||||
" latencies_ms: 14",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" return_codes: 0",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
assertEquals(want, got);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectLogging() throws Exception {
|
||||
asyncDump(100);
|
||||
|
||||
final int OK = 0;
|
||||
Thread[] logActions = {
|
||||
// ignored
|
||||
connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
|
||||
// valid latencies
|
||||
connectEventAction(100, OK, 110, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OK, 23, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OK, 45, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OK, 56, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OK, 523, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OK, 214, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OK, 67, EXAMPLE_IPV6),
|
||||
// errors
|
||||
connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
|
||||
connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
|
||||
connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
|
||||
};
|
||||
|
||||
for (Thread t : logActions) {
|
||||
t.start();
|
||||
}
|
||||
for (Thread t : logActions) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
String got = flushStatistics();
|
||||
String want = String.join("\n",
|
||||
"dropped_events: 0",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 4",
|
||||
" network_id: 100",
|
||||
" time_ms: 0",
|
||||
" transports: 2",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 3",
|
||||
" connect_count: 6",
|
||||
" errnos_counters <",
|
||||
" key: 1",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 11",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 13",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 98",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 110",
|
||||
" value: 2",
|
||||
" >",
|
||||
" ipv6_addr_count: 1",
|
||||
" latencies_ms: 23",
|
||||
" latencies_ms: 45",
|
||||
" latencies_ms: 110",
|
||||
" >",
|
||||
">",
|
||||
"events <",
|
||||
" if_name: \"\"",
|
||||
" link_layer: 2",
|
||||
" network_id: 101",
|
||||
" time_ms: 0",
|
||||
" transports: 1",
|
||||
" connect_statistics <",
|
||||
" connect_blocking_count: 4",
|
||||
" connect_count: 6",
|
||||
" errnos_counters <",
|
||||
" key: 1",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 13",
|
||||
" value: 2",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 110",
|
||||
" value: 1",
|
||||
" >",
|
||||
" errnos_counters <",
|
||||
" key: 111",
|
||||
" value: 1",
|
||||
" >",
|
||||
" ipv6_addr_count: 5",
|
||||
" latencies_ms: 56",
|
||||
" latencies_ms: 67",
|
||||
" latencies_ms: 214",
|
||||
" latencies_ms: 523",
|
||||
" >",
|
||||
">",
|
||||
"version: 2\n");
|
||||
assertEquals(want, got);
|
||||
}
|
||||
|
||||
private void setCapabilities(int netId) {
|
||||
final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
|
||||
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
|
||||
verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
|
||||
networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
|
||||
netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
|
||||
}
|
||||
|
||||
Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
|
||||
setCapabilities(netId);
|
||||
return new Thread(() -> {
|
||||
try {
|
||||
mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
|
||||
} catch (Exception e) {
|
||||
fail(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
|
||||
setCapabilities(netId);
|
||||
mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
|
||||
}
|
||||
|
||||
void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
|
||||
String dstIp, int sport, int dport, long now) throws Exception {
|
||||
String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
|
||||
mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
|
||||
}
|
||||
|
||||
void asyncDump(long durationMs) throws Exception {
|
||||
final long stop = System.currentTimeMillis() + durationMs;
|
||||
final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
|
||||
new Thread(() -> {
|
||||
while (System.currentTimeMillis() < stop) {
|
||||
mService.list(pw);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
|
||||
String flushStatistics() throws Exception {
|
||||
IpConnectivityMetrics metricsService =
|
||||
new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000);
|
||||
metricsService.mNetdListener = mService;
|
||||
|
||||
StringWriter buffer = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(buffer);
|
||||
metricsService.impl.dump(null, writer, new String[]{"flush"});
|
||||
byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT);
|
||||
IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes);
|
||||
for (IpConnectivityEvent ev : log.events) {
|
||||
if (ev.getConnectStatistics() == null) {
|
||||
continue;
|
||||
}
|
||||
// Sort repeated fields of connect() events arriving in non-deterministic order.
|
||||
Arrays.sort(ev.getConnectStatistics().latenciesMs);
|
||||
Arrays.sort(ev.getConnectStatistics().errnosCounters,
|
||||
Comparator.comparingInt((p) -> p.key));
|
||||
}
|
||||
return log.toString();
|
||||
}
|
||||
|
||||
String[] listNetdEvent() throws Exception {
|
||||
StringWriter buffer = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(buffer);
|
||||
mService.list(writer);
|
||||
return buffer.toString().split("\\n");
|
||||
}
|
||||
|
||||
static <T> T[] remove(T[] array, T[] filtered) {
|
||||
List<T> c = Arrays.asList(filtered);
|
||||
int next = 0;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (c.contains(array[i])) {
|
||||
continue;
|
||||
}
|
||||
array[next++] = array[i];
|
||||
}
|
||||
return Arrays.copyOf(array, next);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.server.connectivity;
|
||||
|
||||
import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
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.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityResources;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.AdditionalAnswers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetworkNotificationManagerTest {
|
||||
|
||||
private static final String TEST_SSID = "Test SSID";
|
||||
private static final String TEST_EXTRA_INFO = "extra";
|
||||
static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
|
||||
static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
|
||||
static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
|
||||
static {
|
||||
CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
|
||||
CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
|
||||
WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
|
||||
WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
WIFI_CAPABILITIES.setSSID(TEST_SSID);
|
||||
|
||||
// Set the underyling network to wifi.
|
||||
VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
|
||||
VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
|
||||
VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
|
||||
}
|
||||
|
||||
@Mock Context mCtx;
|
||||
@Mock Resources mResources;
|
||||
@Mock PackageManager mPm;
|
||||
@Mock TelephonyManager mTelephonyManager;
|
||||
@Mock NotificationManager mNotificationManager;
|
||||
@Mock NetworkAgentInfo mWifiNai;
|
||||
@Mock NetworkAgentInfo mCellNai;
|
||||
@Mock NetworkAgentInfo mVpnNai;
|
||||
@Mock NetworkInfo mNetworkInfo;
|
||||
ArgumentCaptor<Notification> mCaptor;
|
||||
|
||||
NetworkNotificationManager mManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mCaptor = ArgumentCaptor.forClass(Notification.class);
|
||||
mWifiNai.networkCapabilities = WIFI_CAPABILITIES;
|
||||
mWifiNai.networkInfo = mNetworkInfo;
|
||||
mCellNai.networkCapabilities = CELL_CAPABILITIES;
|
||||
mCellNai.networkInfo = mNetworkInfo;
|
||||
mVpnNai.networkCapabilities = VPN_CAPABILITIES;
|
||||
mVpnNai.networkInfo = mNetworkInfo;
|
||||
doReturn(true).when(mVpnNai).isVPN();
|
||||
when(mCtx.getResources()).thenReturn(mResources);
|
||||
when(mCtx.getPackageManager()).thenReturn(mPm);
|
||||
when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo());
|
||||
final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mCtx));
|
||||
doReturn(UserHandle.ALL).when(asUserCtx).getUser();
|
||||
when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
|
||||
when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
|
||||
.thenReturn(mNotificationManager);
|
||||
when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO);
|
||||
ConnectivityResources.setResourcesContextForTest(mCtx);
|
||||
when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
|
||||
|
||||
// Come up with some credible-looking transport names. The actual values do not matter.
|
||||
String[] transportNames = new String[NetworkCapabilities.MAX_TRANSPORT + 1];
|
||||
for (int transport = 0; transport <= NetworkCapabilities.MAX_TRANSPORT; transport++) {
|
||||
transportNames[transport] = NetworkCapabilities.transportNameOf(transport);
|
||||
}
|
||||
when(mResources.getStringArray(R.array.network_switch_type_name))
|
||||
.thenReturn(transportNames);
|
||||
|
||||
mManager = new NetworkNotificationManager(mCtx, mTelephonyManager);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ConnectivityResources.setResourcesContextForTest(null);
|
||||
}
|
||||
|
||||
private void verifyTitleByNetwork(final int id, final NetworkAgentInfo nai, final int title) {
|
||||
final String tag = NetworkNotificationManager.tagFor(id);
|
||||
mManager.showNotification(id, PRIVATE_DNS_BROKEN, nai, null, null, true);
|
||||
verify(mNotificationManager, times(1))
|
||||
.notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any());
|
||||
final int transportType = NetworkNotificationManager.approximateTransportType(nai);
|
||||
if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
|
||||
verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO));
|
||||
} else {
|
||||
verify(mResources, times(1)).getString(title);
|
||||
}
|
||||
verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTitleOfPrivateDnsBroken() {
|
||||
// Test the title of mobile data.
|
||||
verifyTitleByNetwork(100, mCellNai, R.string.mobile_no_internet);
|
||||
clearInvocations(mResources);
|
||||
|
||||
// Test the title of wifi.
|
||||
verifyTitleByNetwork(101, mWifiNai, R.string.wifi_no_internet);
|
||||
clearInvocations(mResources);
|
||||
|
||||
// Test the title of other networks.
|
||||
verifyTitleByNetwork(102, mVpnNai, R.string.other_networks_no_internet);
|
||||
clearInvocations(mResources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationsShownAndCleared() {
|
||||
final int NETWORK_ID_BASE = 100;
|
||||
List<NotificationType> types = Arrays.asList(NotificationType.values());
|
||||
List<Integer> ids = new ArrayList<>(types.size());
|
||||
for (int i = 0; i < types.size(); i++) {
|
||||
ids.add(NETWORK_ID_BASE + i);
|
||||
}
|
||||
Collections.shuffle(ids);
|
||||
Collections.shuffle(types);
|
||||
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false);
|
||||
}
|
||||
|
||||
List<Integer> idsToClear = new ArrayList<>(ids);
|
||||
Collections.shuffle(idsToClear);
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
mManager.clearNotification(idsToClear.get(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
final int id = ids.get(i);
|
||||
final int eventId = types.get(i).eventId;
|
||||
final String tag = NetworkNotificationManager.tagFor(id);
|
||||
verify(mNotificationManager, times(1)).notify(eq(tag), eq(eventId), any());
|
||||
verify(mNotificationManager, times(1)).cancel(eq(tag), eq(eventId));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
// Ignored because the code under test calls Log.wtf, which crashes the tests on eng builds.
|
||||
// TODO: re-enable after fixing this (e.g., turn Log.wtf into exceptions that this test catches)
|
||||
public void testNoInternetNotificationsNotShownForCellular() {
|
||||
mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false);
|
||||
mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false);
|
||||
|
||||
verify(mNotificationManager, never()).notify(any(), anyInt(), any());
|
||||
|
||||
mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false);
|
||||
|
||||
final int eventId = NO_INTERNET.eventId;
|
||||
final String tag = NetworkNotificationManager.tagFor(102);
|
||||
verify(mNotificationManager, times(1)).notify(eq(tag), eq(eventId), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationsNotShownIfNoInternetCapability() {
|
||||
mWifiNai.networkCapabilities = new NetworkCapabilities();
|
||||
mWifiNai.networkCapabilities .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
|
||||
mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false);
|
||||
mManager.showNotification(103, LOST_INTERNET, mWifiNai, mCellNai, null, false);
|
||||
mManager.showNotification(104, NETWORK_SWITCH, mWifiNai, mCellNai, null, false);
|
||||
|
||||
verify(mNotificationManager, never()).notify(any(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedNotificationsNoInternetThenSignIn() {
|
||||
final int id = 101;
|
||||
final String tag = NetworkNotificationManager.tagFor(id);
|
||||
|
||||
// Show first NO_INTERNET
|
||||
mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
|
||||
verify(mNotificationManager, times(1)).notify(eq(tag), eq(NO_INTERNET.eventId), any());
|
||||
|
||||
// Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
|
||||
mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
|
||||
verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId));
|
||||
verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any());
|
||||
|
||||
// Network disconnects
|
||||
mManager.clearNotification(id);
|
||||
verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicatedNotificationsSignInThenNoInternet() {
|
||||
final int id = 101;
|
||||
final String tag = NetworkNotificationManager.tagFor(id);
|
||||
|
||||
// Show first SIGN_IN
|
||||
mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
|
||||
verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any());
|
||||
reset(mNotificationManager);
|
||||
|
||||
// NO_INTERNET arrives after, but is ignored.
|
||||
mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
|
||||
verify(mNotificationManager, never()).cancel(any(), anyInt());
|
||||
verify(mNotificationManager, never()).notify(any(), anyInt(), any());
|
||||
|
||||
// Network disconnects
|
||||
mManager.clearNotification(id);
|
||||
verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearNotificationByType() {
|
||||
final int id = 101;
|
||||
final String tag = NetworkNotificationManager.tagFor(id);
|
||||
|
||||
// clearNotification(int id, NotificationType notifyType) will check if given type is equal
|
||||
// to previous type or not. If they are equal then clear the notification; if they are not
|
||||
// equal then return.
|
||||
mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
|
||||
verify(mNotificationManager, times(1)).notify(eq(tag), eq(NO_INTERNET.eventId), any());
|
||||
|
||||
// Previous notification is NO_INTERNET and given type is NO_INTERNET too. The notification
|
||||
// should be cleared.
|
||||
mManager.clearNotification(id, NO_INTERNET);
|
||||
verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId));
|
||||
|
||||
// SIGN_IN is popped-up.
|
||||
mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
|
||||
verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any());
|
||||
|
||||
// The notification type is not matching previous one, PARTIAL_CONNECTIVITY won't be
|
||||
// cleared.
|
||||
mManager.clearNotification(id, PARTIAL_CONNECTIVITY);
|
||||
verify(mNotificationManager, never()).cancel(eq(tag), eq(PARTIAL_CONNECTIVITY.eventId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.server.connectivity
|
||||
|
||||
import android.net.NetworkRequest
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.mock
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class NetworkRankerTest {
|
||||
private val ranker = NetworkRanker()
|
||||
|
||||
private fun makeNai(satisfy: Boolean, score: Int) = mock(NetworkAgentInfo::class.java).also {
|
||||
doReturn(satisfy).`when`(it).satisfies(any())
|
||||
doReturn(score).`when`(it).currentScore
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetBestNetwork() {
|
||||
val scores = listOf(20, 50, 90, 60, 23, 68)
|
||||
val nais = scores.map { makeNai(true, it) }
|
||||
val bestNetwork = nais[2] // The one with the top score
|
||||
val someRequest = mock(NetworkRequest::class.java)
|
||||
assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoreNonSatisfying() {
|
||||
val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90),
|
||||
makeNai(false, 60), makeNai(true, 23), makeNai(false, 68))
|
||||
val bestNetwork = nais[1] // Top score that's satisfying
|
||||
val someRequest = mock(NetworkRequest::class.java)
|
||||
assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoMatch() {
|
||||
val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90))
|
||||
val someRequest = mock(NetworkRequest::class.java)
|
||||
assertNull(ranker.getBestNetwork(someRequest, nais))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmpty() {
|
||||
val someRequest = mock(NetworkRequest::class.java)
|
||||
assertNull(ranker.getBestNetwork(someRequest, emptyList()))
|
||||
}
|
||||
|
||||
// Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST
|
||||
// network satisfying the request if multiple of them have the same score.
|
||||
@Test
|
||||
fun testStable() {
|
||||
val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30),
|
||||
makeNai(true, 30), makeNai(true, 30), makeNai(true, 30))
|
||||
val someRequest = mock(NetworkRequest::class.java)
|
||||
assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1))
|
||||
|
||||
val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20),
|
||||
makeNai(true, 50), makeNai(true, 50), makeNai(true, 40))
|
||||
assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,803 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.server.connectivity;
|
||||
|
||||
import static android.Manifest.permission.CHANGE_NETWORK_STATE;
|
||||
import static android.Manifest.permission.CHANGE_WIFI_STATE;
|
||||
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
|
||||
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
|
||||
import static android.Manifest.permission.INTERNET;
|
||||
import static android.Manifest.permission.NETWORK_STACK;
|
||||
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
|
||||
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM;
|
||||
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT;
|
||||
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
|
||||
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
|
||||
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
|
||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||
import static android.content.pm.PackageManager.MATCH_ANY_USER;
|
||||
import static android.os.Process.SYSTEM_UID;
|
||||
|
||||
import static com.android.server.connectivity.PermissionMonitor.NETWORK;
|
||||
import static com.android.server.connectivity.PermissionMonitor.SYSTEM;
|
||||
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.AdditionalMatchers.aryEq;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.INetd;
|
||||
import android.net.UidRange;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.SystemConfigManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.AdditionalAnswers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class PermissionMonitorTest {
|
||||
private static final UserHandle MOCK_USER1 = UserHandle.of(0);
|
||||
private static final UserHandle MOCK_USER2 = UserHandle.of(1);
|
||||
private static final int MOCK_UID1 = 10001;
|
||||
private static final int MOCK_UID2 = 10086;
|
||||
private static final int SYSTEM_UID1 = 1000;
|
||||
private static final int SYSTEM_UID2 = 1008;
|
||||
private static final int VPN_UID = 10002;
|
||||
private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
|
||||
private static final String MOCK_PACKAGE1 = "appName1";
|
||||
private static final String MOCK_PACKAGE2 = "appName2";
|
||||
private static final String SYSTEM_PACKAGE1 = "sysName1";
|
||||
private static final String SYSTEM_PACKAGE2 = "sysName2";
|
||||
private static final String PARTITION_SYSTEM = "system";
|
||||
private static final String PARTITION_OEM = "oem";
|
||||
private static final String PARTITION_PRODUCT = "product";
|
||||
private static final String PARTITION_VENDOR = "vendor";
|
||||
private static final int VERSION_P = Build.VERSION_CODES.P;
|
||||
private static final int VERSION_Q = Build.VERSION_CODES.Q;
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private PackageManager mPackageManager;
|
||||
@Mock private INetd mNetdService;
|
||||
@Mock private UserManager mUserManager;
|
||||
@Mock private PermissionMonitor.Dependencies mDeps;
|
||||
@Mock private SystemConfigManager mSystemConfigManager;
|
||||
|
||||
private PermissionMonitor mPermissionMonitor;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
|
||||
when(mUserManager.getUserHandles(eq(true))).thenReturn(
|
||||
Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 }));
|
||||
when(mContext.getSystemServiceName(SystemConfigManager.class))
|
||||
.thenReturn(Context.SYSTEM_CONFIG_SERVICE);
|
||||
when(mContext.getSystemService(Context.SYSTEM_CONFIG_SERVICE))
|
||||
.thenReturn(mSystemConfigManager);
|
||||
when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]);
|
||||
final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
|
||||
doReturn(UserHandle.ALL).when(asUserCtx).getUser();
|
||||
when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
|
||||
|
||||
mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
|
||||
|
||||
when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(/* empty app list */ null);
|
||||
mPermissionMonitor.startMonitoring();
|
||||
}
|
||||
|
||||
private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid,
|
||||
String... permissions) {
|
||||
final PackageInfo packageInfo =
|
||||
packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, permissions, partition);
|
||||
packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
|
||||
packageInfo.applicationInfo.uid = uid;
|
||||
return mPermissionMonitor.hasRestrictedNetworkPermission(packageInfo);
|
||||
}
|
||||
|
||||
private static PackageInfo systemPackageInfoWithPermissions(String... permissions) {
|
||||
return packageInfoWithPermissions(
|
||||
REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM);
|
||||
}
|
||||
|
||||
private static PackageInfo vendorPackageInfoWithPermissions(String... permissions) {
|
||||
return packageInfoWithPermissions(
|
||||
REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_VENDOR);
|
||||
}
|
||||
|
||||
private static PackageInfo packageInfoWithPermissions(int permissionsFlags,
|
||||
String[] permissions, String partition) {
|
||||
int[] requestedPermissionsFlags = new int[permissions.length];
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
requestedPermissionsFlags[i] = permissionsFlags;
|
||||
}
|
||||
final PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.requestedPermissions = permissions;
|
||||
packageInfo.applicationInfo = new ApplicationInfo();
|
||||
packageInfo.requestedPermissionsFlags = requestedPermissionsFlags;
|
||||
int privateFlags = 0;
|
||||
switch (partition) {
|
||||
case PARTITION_OEM:
|
||||
privateFlags = PRIVATE_FLAG_OEM;
|
||||
break;
|
||||
case PARTITION_PRODUCT:
|
||||
privateFlags = PRIVATE_FLAG_PRODUCT;
|
||||
break;
|
||||
case PARTITION_VENDOR:
|
||||
privateFlags = PRIVATE_FLAG_VENDOR;
|
||||
break;
|
||||
}
|
||||
packageInfo.applicationInfo.privateFlags = privateFlags;
|
||||
return packageInfo;
|
||||
}
|
||||
|
||||
private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid,
|
||||
UserHandle user) {
|
||||
final PackageInfo pkgInfo;
|
||||
if (hasSystemPermission) {
|
||||
pkgInfo = systemPackageInfoWithPermissions(
|
||||
CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
|
||||
} else {
|
||||
pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, "");
|
||||
}
|
||||
pkgInfo.applicationInfo.uid = user.getUid(UserHandle.getAppId(uid));
|
||||
return pkgInfo;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasPermission() {
|
||||
PackageInfo app = systemPackageInfoWithPermissions();
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
|
||||
|
||||
app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE, NETWORK_STACK);
|
||||
assertTrue(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
|
||||
assertTrue(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
|
||||
|
||||
app = systemPackageInfoWithPermissions(
|
||||
CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL);
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
|
||||
assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
|
||||
|
||||
app = packageInfoWithPermissions(REQUESTED_PERMISSION_REQUIRED, new String[] {
|
||||
CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL, NETWORK_STACK },
|
||||
PARTITION_SYSTEM);
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
|
||||
|
||||
app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
|
||||
app.requestedPermissions = null;
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
|
||||
|
||||
app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
|
||||
app.requestedPermissionsFlags = null;
|
||||
assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsVendorApp() {
|
||||
PackageInfo app = systemPackageInfoWithPermissions();
|
||||
assertFalse(mPermissionMonitor.isVendorApp(app.applicationInfo));
|
||||
app = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED,
|
||||
new String[] {}, PARTITION_OEM);
|
||||
assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
|
||||
app = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED,
|
||||
new String[] {}, PARTITION_PRODUCT);
|
||||
assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
|
||||
app = vendorPackageInfoWithPermissions();
|
||||
assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasNetworkPermission() {
|
||||
PackageInfo app = systemPackageInfoWithPermissions();
|
||||
assertFalse(mPermissionMonitor.hasNetworkPermission(app));
|
||||
app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE);
|
||||
assertTrue(mPermissionMonitor.hasNetworkPermission(app));
|
||||
app = systemPackageInfoWithPermissions(NETWORK_STACK);
|
||||
assertFalse(mPermissionMonitor.hasNetworkPermission(app));
|
||||
app = systemPackageInfoWithPermissions(CONNECTIVITY_USE_RESTRICTED_NETWORKS);
|
||||
assertFalse(mPermissionMonitor.hasNetworkPermission(app));
|
||||
app = systemPackageInfoWithPermissions(CONNECTIVITY_INTERNAL);
|
||||
assertFalse(mPermissionMonitor.hasNetworkPermission(app));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasRestrictedNetworkPermission() {
|
||||
assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
|
||||
assertFalse(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_P, MOCK_UID1, NETWORK_STACK));
|
||||
assertFalse(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
assertFalse(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
|
||||
|
||||
assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
|
||||
assertFalse(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasRestrictedNetworkPermissionSystemUid() {
|
||||
doReturn(VERSION_P).when(mDeps).getDeviceFirstSdkInt();
|
||||
assertTrue(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_INTERNAL));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
|
||||
doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
|
||||
assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
|
||||
assertFalse(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_INTERNAL));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasRestrictedNetworkPermissionVendorApp() {
|
||||
assertTrue(hasRestrictedNetworkPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_VENDOR, VERSION_P, MOCK_UID1, NETWORK_STACK));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
assertTrue(hasRestrictedNetworkPermission(
|
||||
PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
|
||||
|
||||
assertFalse(hasRestrictedNetworkPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
|
||||
assertFalse(hasRestrictedNetworkPermission(
|
||||
PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL));
|
||||
assertFalse(hasRestrictedNetworkPermission(
|
||||
PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_NETWORK_STATE));
|
||||
}
|
||||
|
||||
private void assertBackgroundPermission(boolean hasPermission, String name, int uid,
|
||||
String... permissions) throws Exception {
|
||||
when(mPackageManager.getPackageInfo(eq(name), anyInt()))
|
||||
.thenReturn(packageInfoWithPermissions(
|
||||
REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM));
|
||||
mPermissionMonitor.onPackageAdded(name, uid);
|
||||
assertEquals(hasPermission, mPermissionMonitor.hasUseBackgroundNetworksPermission(uid));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasUseBackgroundNetworksPermission() throws Exception {
|
||||
assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(SYSTEM_UID));
|
||||
assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID);
|
||||
assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID, CONNECTIVITY_INTERNAL);
|
||||
assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, CHANGE_NETWORK_STATE);
|
||||
assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, NETWORK_STACK);
|
||||
|
||||
assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID1));
|
||||
assertBackgroundPermission(false, MOCK_PACKAGE1, MOCK_UID1);
|
||||
assertBackgroundPermission(true, MOCK_PACKAGE1, MOCK_UID1,
|
||||
CONNECTIVITY_USE_RESTRICTED_NETWORKS);
|
||||
|
||||
assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID2));
|
||||
assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2);
|
||||
assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2,
|
||||
CONNECTIVITY_INTERNAL);
|
||||
assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID2, NETWORK_STACK);
|
||||
}
|
||||
|
||||
private class NetdMonitor {
|
||||
private final HashMap<Integer, Boolean> mApps = new HashMap<>();
|
||||
|
||||
NetdMonitor(INetd mockNetd) throws Exception {
|
||||
// Add hook to verify and track result of setPermission.
|
||||
doAnswer((InvocationOnMock invocation) -> {
|
||||
final Object[] args = invocation.getArguments();
|
||||
final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM);
|
||||
for (final int uid : (int[]) args[1]) {
|
||||
// TODO: Currently, permission monitor will send duplicate commands for each uid
|
||||
// corresponding to each user. Need to fix that and uncomment below test.
|
||||
// if (mApps.containsKey(uid) && mApps.get(uid) == isSystem) {
|
||||
// fail("uid " + uid + " is already set to " + isSystem);
|
||||
// }
|
||||
mApps.put(uid, isSystem);
|
||||
}
|
||||
return null;
|
||||
}).when(mockNetd).networkSetPermissionForUser(anyInt(), any(int[].class));
|
||||
|
||||
// Add hook to verify and track result of clearPermission.
|
||||
doAnswer((InvocationOnMock invocation) -> {
|
||||
final Object[] args = invocation.getArguments();
|
||||
for (final int uid : (int[]) args[0]) {
|
||||
// TODO: Currently, permission monitor will send duplicate commands for each uid
|
||||
// corresponding to each user. Need to fix that and uncomment below test.
|
||||
// if (!mApps.containsKey(uid)) {
|
||||
// fail("uid " + uid + " does not exist.");
|
||||
// }
|
||||
mApps.remove(uid);
|
||||
}
|
||||
return null;
|
||||
}).when(mockNetd).networkClearPermissionForUser(any(int[].class));
|
||||
}
|
||||
|
||||
public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) {
|
||||
for (final UserHandle user : users) {
|
||||
for (final int app : apps) {
|
||||
final int uid = user.getUid(app);
|
||||
if (!mApps.containsKey(uid)) {
|
||||
fail("uid " + uid + " does not exist.");
|
||||
}
|
||||
if (mApps.get(uid) != permission) {
|
||||
fail("uid " + uid + " has wrong permission: " + permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void expectNoPermission(UserHandle[] users, int[] apps) {
|
||||
for (final UserHandle user : users) {
|
||||
for (final int app : apps) {
|
||||
final int uid = user.getUid(app);
|
||||
if (mApps.containsKey(uid)) {
|
||||
fail("uid " + uid + " has listed permissions, expected none.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserAndPackageAddRemove() throws Exception {
|
||||
final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
|
||||
|
||||
// MOCK_UID1: MOCK_PACKAGE1 only has network permission.
|
||||
// SYSTEM_UID: SYSTEM_PACKAGE1 has system permission.
|
||||
// SYSTEM_UID: SYSTEM_PACKAGE2 only has network permission.
|
||||
doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM), anyString());
|
||||
doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(any(),
|
||||
eq(SYSTEM_PACKAGE1));
|
||||
doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
|
||||
eq(SYSTEM_PACKAGE2));
|
||||
doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
|
||||
eq(MOCK_PACKAGE1));
|
||||
|
||||
// Add SYSTEM_PACKAGE2, expect only have network permission.
|
||||
mPermissionMonitor.onUserAdded(MOCK_USER1);
|
||||
addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
|
||||
mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
|
||||
|
||||
// Add SYSTEM_PACKAGE1, expect permission escalate.
|
||||
addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
|
||||
mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID});
|
||||
|
||||
mPermissionMonitor.onUserAdded(MOCK_USER2);
|
||||
mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID});
|
||||
|
||||
addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
|
||||
mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID});
|
||||
mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{MOCK_UID1});
|
||||
|
||||
// Remove MOCK_UID1, expect no permission left for all user.
|
||||
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
|
||||
removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
|
||||
mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{MOCK_UID1});
|
||||
|
||||
// Remove SYSTEM_PACKAGE1, expect permission downgrade.
|
||||
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
|
||||
removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
SYSTEM_PACKAGE1, SYSTEM_UID);
|
||||
mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID});
|
||||
|
||||
mPermissionMonitor.onUserRemoved(MOCK_USER1);
|
||||
mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID});
|
||||
|
||||
// Remove all packages, expect no permission left.
|
||||
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
|
||||
removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
|
||||
mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID, MOCK_UID1});
|
||||
|
||||
// Remove last user, expect no redundant clearPermission is invoked.
|
||||
mPermissionMonitor.onUserRemoved(MOCK_USER2);
|
||||
mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID, MOCK_UID1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
|
||||
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
|
||||
Arrays.asList(new PackageInfo[] {
|
||||
buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1),
|
||||
buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1),
|
||||
buildPackageInfo(false /* hasSystemPermission */, MOCK_UID2, MOCK_USER1),
|
||||
buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1)
|
||||
}));
|
||||
when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1),
|
||||
eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
|
||||
buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1));
|
||||
mPermissionMonitor.startMonitoring();
|
||||
// Every app on user 0 except MOCK_UID2 are under VPN.
|
||||
final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
|
||||
new UidRange(0, MOCK_UID2 - 1),
|
||||
new UidRange(MOCK_UID2 + 1, UserHandle.PER_USER_RANGE - 1)}));
|
||||
final Set<UidRange> vpnRange2 = Collections.singleton(new UidRange(MOCK_UID2, MOCK_UID2));
|
||||
|
||||
// When VPN is connected, expect a rule to be set up for user app MOCK_UID1
|
||||
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID1}));
|
||||
|
||||
reset(mNetdService);
|
||||
|
||||
// When MOCK_UID1 package is uninstalled and reinstalled, expect Netd to be updated
|
||||
mPermissionMonitor.onPackageRemoved(
|
||||
MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1));
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
|
||||
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1));
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID1}));
|
||||
|
||||
reset(mNetdService);
|
||||
|
||||
// During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
|
||||
// old UID rules then adds the new ones. Expect netd to be updated
|
||||
mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
|
||||
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID2}));
|
||||
|
||||
reset(mNetdService);
|
||||
|
||||
// When VPN is disconnected, expect rules to be torn down
|
||||
mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID2}));
|
||||
assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
|
||||
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
|
||||
Arrays.asList(new PackageInfo[] {
|
||||
buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1),
|
||||
buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1)
|
||||
}));
|
||||
when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1),
|
||||
eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
|
||||
buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1));
|
||||
|
||||
mPermissionMonitor.startMonitoring();
|
||||
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
|
||||
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID);
|
||||
|
||||
// Newly-installed package should have uid rules added
|
||||
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1));
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID1}));
|
||||
|
||||
// Removed package should have its uid rules removed
|
||||
mPermissionMonitor.onPackageRemoved(
|
||||
MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1));
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
|
||||
}
|
||||
|
||||
|
||||
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
|
||||
// each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
|
||||
// called multiple times with the uid corresponding to each user.
|
||||
private void addPackageForUsers(UserHandle[] users, String packageName, int uid) {
|
||||
for (final UserHandle user : users) {
|
||||
mPermissionMonitor.onPackageAdded(packageName, user.getUid(uid));
|
||||
}
|
||||
}
|
||||
|
||||
private void removePackageForUsers(UserHandle[] users, String packageName, int uid) {
|
||||
for (final UserHandle user : users) {
|
||||
mPermissionMonitor.onPackageRemoved(packageName, user.getUid(uid));
|
||||
}
|
||||
}
|
||||
|
||||
private class NetdServiceMonitor {
|
||||
private final HashMap<Integer, Integer> mPermissions = new HashMap<>();
|
||||
|
||||
NetdServiceMonitor(INetd mockNetdService) throws Exception {
|
||||
// Add hook to verify and track result of setPermission.
|
||||
doAnswer((InvocationOnMock invocation) -> {
|
||||
final Object[] args = invocation.getArguments();
|
||||
final int permission = (int) args[0];
|
||||
for (final int uid : (int[]) args[1]) {
|
||||
mPermissions.put(uid, permission);
|
||||
}
|
||||
return null;
|
||||
}).when(mockNetdService).trafficSetNetPermForUids(anyInt(), any(int[].class));
|
||||
}
|
||||
|
||||
public void expectPermission(int permission, int[] apps) {
|
||||
for (final int app : apps) {
|
||||
if (!mPermissions.containsKey(app)) {
|
||||
fail("uid " + app + " does not exist.");
|
||||
}
|
||||
if (mPermissions.get(app) != permission) {
|
||||
fail("uid " + app + " has wrong permission: " + mPermissions.get(app));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackagePermissionUpdate() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
// MOCK_UID1: MOCK_PACKAGE1 only has internet permission.
|
||||
// MOCK_UID2: MOCK_PACKAGE2 does not have any permission.
|
||||
// SYSTEM_UID1: SYSTEM_PACKAGE1 has internet permission and update device stats permission.
|
||||
// SYSTEM_UID2: SYSTEM_PACKAGE2 has only update device stats permission.
|
||||
|
||||
SparseIntArray netdPermissionsAppIds = new SparseIntArray();
|
||||
netdPermissionsAppIds.put(MOCK_UID1, INetd.PERMISSION_INTERNET);
|
||||
netdPermissionsAppIds.put(MOCK_UID2, INetd.PERMISSION_NONE);
|
||||
netdPermissionsAppIds.put(SYSTEM_UID1, INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS);
|
||||
netdPermissionsAppIds.put(SYSTEM_UID2, INetd.PERMISSION_UPDATE_DEVICE_STATS);
|
||||
|
||||
// Send the permission information to netd, expect permission updated.
|
||||
mPermissionMonitor.sendPackagePermissionsToNetd(netdPermissionsAppIds);
|
||||
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET,
|
||||
new int[]{MOCK_UID1});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID2});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UPDATE_DEVICE_STATS,
|
||||
new int[]{SYSTEM_UID2});
|
||||
|
||||
// Update permission of MOCK_UID1, expect new permission show up.
|
||||
mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1,
|
||||
INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
|
||||
|
||||
// Change permissions of SYSTEM_UID2, expect new permission show up and old permission
|
||||
// revoked.
|
||||
mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2,
|
||||
INetd.PERMISSION_INTERNET);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{SYSTEM_UID2});
|
||||
|
||||
// Revoke permission from SYSTEM_UID1, expect no permission stored.
|
||||
mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, INetd.PERMISSION_NONE);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1});
|
||||
}
|
||||
|
||||
private PackageInfo setPackagePermissions(String packageName, int uid, String[] permissions)
|
||||
throws Exception {
|
||||
PackageInfo packageInfo = packageInfoWithPermissions(
|
||||
REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM);
|
||||
when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo);
|
||||
when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName});
|
||||
return packageInfo;
|
||||
}
|
||||
|
||||
private PackageInfo addPackage(String packageName, int uid, String[] permissions)
|
||||
throws Exception {
|
||||
PackageInfo packageInfo = setPackagePermissions(packageName, uid, permissions);
|
||||
mPermissionMonitor.onPackageAdded(packageName, uid);
|
||||
return packageInfo;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageInstall() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
|
||||
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
|
||||
|
||||
addPackage(MOCK_PACKAGE2, MOCK_UID2, new String[] {INTERNET});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID2});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageInstallSharedUid() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
|
||||
PackageInfo packageInfo1 = addPackage(MOCK_PACKAGE1, MOCK_UID1,
|
||||
new String[] {INTERNET, UPDATE_DEVICE_STATS});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
|
||||
|
||||
// Install another package with the same uid and no permissions should not cause the UID to
|
||||
// lose permissions.
|
||||
PackageInfo packageInfo2 = systemPackageInfoWithPermissions();
|
||||
when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
|
||||
when(mPackageManager.getPackagesForUid(MOCK_UID1))
|
||||
.thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
|
||||
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE2, MOCK_UID1);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageUninstallBasic() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
|
||||
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
|
||||
|
||||
when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
|
||||
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageRemoveThenAdd() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
|
||||
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
|
||||
|
||||
when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
|
||||
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
|
||||
|
||||
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageUpdate() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
|
||||
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID1});
|
||||
|
||||
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageUninstallWithMultiplePackages() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
|
||||
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
|
||||
|
||||
// Mock another package with the same uid but different permissions.
|
||||
PackageInfo packageInfo2 = systemPackageInfoWithPermissions(INTERNET);
|
||||
when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
|
||||
when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{
|
||||
MOCK_PACKAGE2});
|
||||
|
||||
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealSystemPermission() throws Exception {
|
||||
// Use the real context as this test must ensure the *real* system package holds the
|
||||
// necessary permission.
|
||||
final Context realContext = InstrumentationRegistry.getContext();
|
||||
final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
|
||||
final PackageManager manager = realContext.getPackageManager();
|
||||
final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
|
||||
GET_PERMISSIONS | MATCH_ANY_USER);
|
||||
assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateUidPermissionsFromSystemConfig() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(new ArrayList<>());
|
||||
when(mSystemConfigManager.getSystemPermissionUids(eq(INTERNET)))
|
||||
.thenReturn(new int[]{ MOCK_UID1, MOCK_UID2 });
|
||||
when(mSystemConfigManager.getSystemPermissionUids(eq(UPDATE_DEVICE_STATS)))
|
||||
.thenReturn(new int[]{ MOCK_UID2 });
|
||||
|
||||
mPermissionMonitor.startMonitoring();
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{ MOCK_UID1 });
|
||||
mNetdServiceMonitor.expectPermission(
|
||||
INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
|
||||
new int[]{ MOCK_UID2 });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntentReceiver() throws Exception {
|
||||
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
|
||||
final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(), any(), any(), any());
|
||||
final BroadcastReceiver receiver = receiverCaptor.getValue();
|
||||
|
||||
// Verify receiving PACKAGE_ADDED intent.
|
||||
final Intent addedIntent = new Intent(Intent.ACTION_PACKAGE_ADDED,
|
||||
Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */));
|
||||
addedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID1);
|
||||
setPackagePermissions(MOCK_PACKAGE1, MOCK_UID1,
|
||||
new String[] { INTERNET, UPDATE_DEVICE_STATS });
|
||||
receiver.onReceive(mContext, addedIntent);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
|
||||
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[] { MOCK_UID1 });
|
||||
|
||||
// Verify receiving PACKAGE_REMOVED intent.
|
||||
when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(null);
|
||||
final Intent removedIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED,
|
||||
Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */));
|
||||
removedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID1);
|
||||
receiver.onReceive(mContext, removedIntent);
|
||||
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[] { MOCK_UID1 });
|
||||
}
|
||||
|
||||
}
|
||||
1283
tests/unit/java/com/android/server/connectivity/VpnTest.java
Normal file
1283
tests/unit/java/com/android/server/connectivity/VpnTest.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.server.net;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.Manifest.permission;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetworkStatsAccessTest {
|
||||
private static final String TEST_PKG = "com.example.test";
|
||||
private static final int TEST_UID = 12345;
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private DevicePolicyManagerInternal mDpmi;
|
||||
@Mock private TelephonyManager mTm;
|
||||
@Mock private AppOpsManager mAppOps;
|
||||
|
||||
// Hold the real service so we can restore it when tearing down the test.
|
||||
private DevicePolicyManagerInternal mSystemDpmi;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
|
||||
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
|
||||
LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi);
|
||||
|
||||
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm);
|
||||
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
|
||||
LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
|
||||
setHasCarrierPrivileges(true);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEVICE,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_isDeviceOwner() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(true);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEVICE,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_isProfileOwner() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.USER,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(true);
|
||||
assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEFAULT,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEFAULT,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
private void setHasCarrierPrivileges(boolean hasPrivileges) {
|
||||
when(mTm.checkCarrierPrivilegesForPackageAnyPhone(TEST_PKG)).thenReturn(
|
||||
hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
|
||||
: TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
|
||||
}
|
||||
|
||||
private void setIsDeviceOwner(boolean isOwner) {
|
||||
when(mDpmi.isActiveDeviceOwner(TEST_UID)).thenReturn(isOwner);
|
||||
}
|
||||
|
||||
private void setIsProfileOwner(boolean isOwner) {
|
||||
when(mDpmi.isActiveProfileOwner(TEST_UID)).thenReturn(isOwner);
|
||||
}
|
||||
|
||||
private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
|
||||
when(mAppOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG))
|
||||
.thenReturn(appOpsMode);
|
||||
when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
|
||||
hasPermission ? PackageManager.PERMISSION_GRANTED
|
||||
: PackageManager.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
private void setHasReadHistoryPermission(boolean hasPermission) {
|
||||
when(mContext.checkCallingOrSelfPermission(permission.READ_NETWORK_USAGE_HISTORY))
|
||||
.thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
|
||||
: PackageManager.PERMISSION_DENIED);
|
||||
}
|
||||
}
|
||||
119
tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
Normal file
119
tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.server.net;
|
||||
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
|
||||
import static android.net.NetworkStats.METERED_ALL;
|
||||
import static android.net.NetworkStats.METERED_NO;
|
||||
import static android.net.NetworkStats.METERED_YES;
|
||||
import static android.net.NetworkStats.ROAMING_ALL;
|
||||
import static android.net.NetworkStats.ROAMING_NO;
|
||||
import static android.net.NetworkStats.ROAMING_YES;
|
||||
import static android.net.NetworkStats.SET_ALL;
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.SET_FOREGROUND;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.net.NetworkStats;
|
||||
import android.net.UnderlyingNetworkInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Superclass with utilities for NetworkStats(Service|Factory)Test */
|
||||
abstract class NetworkStatsBaseTest {
|
||||
static final String TEST_IFACE = "test0";
|
||||
static final String TEST_IFACE2 = "test1";
|
||||
static final String TUN_IFACE = "test_nss_tun0";
|
||||
static final String TUN_IFACE2 = "test_nss_tun1";
|
||||
|
||||
static final int UID_RED = 1001;
|
||||
static final int UID_BLUE = 1002;
|
||||
static final int UID_GREEN = 1003;
|
||||
static final int UID_VPN = 1004;
|
||||
|
||||
void assertValues(NetworkStats stats, String iface, int uid, long rxBytes,
|
||||
long rxPackets, long txBytes, long txPackets) {
|
||||
assertValues(
|
||||
stats, iface, uid, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
|
||||
rxBytes, rxPackets, txBytes, txPackets, 0);
|
||||
}
|
||||
|
||||
void assertValues(NetworkStats stats, String iface, int uid, int set, int tag,
|
||||
int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
|
||||
long txBytes, long txPackets, long operations) {
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
final int[] sets;
|
||||
if (set == SET_ALL) {
|
||||
sets = new int[] {SET_ALL, SET_DEFAULT, SET_FOREGROUND};
|
||||
} else {
|
||||
sets = new int[] {set};
|
||||
}
|
||||
|
||||
final int[] roamings;
|
||||
if (roaming == ROAMING_ALL) {
|
||||
roamings = new int[] {ROAMING_ALL, ROAMING_YES, ROAMING_NO};
|
||||
} else {
|
||||
roamings = new int[] {roaming};
|
||||
}
|
||||
|
||||
final int[] meterings;
|
||||
if (metered == METERED_ALL) {
|
||||
meterings = new int[] {METERED_ALL, METERED_YES, METERED_NO};
|
||||
} else {
|
||||
meterings = new int[] {metered};
|
||||
}
|
||||
|
||||
final int[] defaultNetworks;
|
||||
if (defaultNetwork == DEFAULT_NETWORK_ALL) {
|
||||
defaultNetworks =
|
||||
new int[] {DEFAULT_NETWORK_ALL, DEFAULT_NETWORK_YES, DEFAULT_NETWORK_NO};
|
||||
} else {
|
||||
defaultNetworks = new int[] {defaultNetwork};
|
||||
}
|
||||
|
||||
for (int s : sets) {
|
||||
for (int r : roamings) {
|
||||
for (int m : meterings) {
|
||||
for (int d : defaultNetworks) {
|
||||
final int i = stats.findIndex(iface, uid, s, tag, m, r, d);
|
||||
if (i != -1) {
|
||||
entry.add(stats.getValues(i, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
|
||||
assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
|
||||
assertEquals("unexpected txBytes", txBytes, entry.txBytes);
|
||||
assertEquals("unexpected txPackets", txPackets, entry.txPackets);
|
||||
assertEquals("unexpected operations", operations, entry.operations);
|
||||
}
|
||||
|
||||
static UnderlyingNetworkInfo createVpnInfo(String[] underlyingIfaces) {
|
||||
return createVpnInfo(TUN_IFACE, underlyingIfaces);
|
||||
}
|
||||
|
||||
static UnderlyingNetworkInfo createVpnInfo(String vpnIface, String[] underlyingIfaces) {
|
||||
return new UnderlyingNetworkInfo(UID_VPN, vpnIface, Arrays.asList(underlyingIfaces));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.server.net;
|
||||
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||
import static android.net.NetworkIdentity.OEM_NONE;
|
||||
import static android.net.NetworkStats.SET_ALL;
|
||||
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.NetworkStatsHistory.FIELD_ALL;
|
||||
import static android.net.NetworkTemplate.buildTemplateMobileAll;
|
||||
import static android.os.Process.myUid;
|
||||
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational;
|
||||
import static com.android.testutils.MiscAsserts.assertThrows;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkIdentity;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.telephony.SubscriptionPlan;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.RecurrenceRule;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.frameworks.tests.net.R;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.io.Streams;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for {@link NetworkStatsCollection}.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetworkStatsCollectionTest {
|
||||
|
||||
private static final String TEST_FILE = "test.bin";
|
||||
private static final String TEST_IMSI = "310260000000000";
|
||||
|
||||
private static final long TIME_A = 1326088800000L; // UTC: Monday 9th January 2012 06:00:00 AM
|
||||
private static final long TIME_B = 1326110400000L; // UTC: Monday 9th January 2012 12:00:00 PM
|
||||
private static final long TIME_C = 1326132000000L; // UTC: Monday 9th January 2012 06:00:00 PM
|
||||
|
||||
private static Clock sOriginalClock;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
sOriginalClock = RecurrenceRule.sClock;
|
||||
// ignore any device overlay while testing
|
||||
NetworkTemplate.forceAllNetworkTypes();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
RecurrenceRule.sClock = sOriginalClock;
|
||||
NetworkTemplate.resetForceAllNetworkTypes();
|
||||
}
|
||||
|
||||
private void setClock(Instant instant) {
|
||||
RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLegacyNetwork() throws Exception {
|
||||
final File testFile =
|
||||
new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
|
||||
stageFile(R.raw.netstats_v1, testFile);
|
||||
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
|
||||
collection.readLegacyNetwork(testFile);
|
||||
|
||||
// verify that history read correctly
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// now export into a unified format
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
collection.write(bos);
|
||||
|
||||
// clear structure completely
|
||||
collection.reset();
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// and read back into structure, verifying that totals are same
|
||||
collection.read(new ByteArrayInputStream(bos.toByteArray()));
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLegacyUid() throws Exception {
|
||||
final File testFile =
|
||||
new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
|
||||
stageFile(R.raw.netstats_uid_v4, testFile);
|
||||
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
|
||||
collection.readLegacyUid(testFile, false);
|
||||
|
||||
// verify that history read correctly
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// now export into a unified format
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
collection.write(bos);
|
||||
|
||||
// clear structure completely
|
||||
collection.reset();
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// and read back into structure, verifying that totals are same
|
||||
collection.read(new ByteArrayInputStream(bos.toByteArray()));
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLegacyUidTags() throws Exception {
|
||||
final File testFile =
|
||||
new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
|
||||
stageFile(R.raw.netstats_uid_v4, testFile);
|
||||
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
|
||||
collection.readLegacyUid(testFile, true);
|
||||
|
||||
// verify that history read correctly
|
||||
assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
77017831L, 100995L, 35436758L, 92344L);
|
||||
|
||||
// now export into a unified format
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
collection.write(bos);
|
||||
|
||||
// clear structure completely
|
||||
collection.reset();
|
||||
assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
0L, 0L, 0L, 0L);
|
||||
|
||||
// and read back into structure, verifying that totals are same
|
||||
collection.read(new ByteArrayInputStream(bos.toByteArray()));
|
||||
assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
77017831L, 100995L, 35436758L, 92344L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartEndAtomicBuckets() throws Exception {
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
|
||||
|
||||
// record empty data straddling between buckets
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
entry.rxBytes = 32;
|
||||
collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS,
|
||||
90 * MINUTE_IN_MILLIS, entry);
|
||||
|
||||
// assert that we report boundary in atomic buckets
|
||||
assertEquals(0, collection.getStartMillis());
|
||||
assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccessLevels() throws Exception {
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
final NetworkIdentitySet identSet = new NetworkIdentitySet();
|
||||
identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
|
||||
TEST_IMSI, null, false, true, true, OEM_NONE));
|
||||
|
||||
int myUid = Process.myUid();
|
||||
int otherUidInSameUser = Process.myUid() + 1;
|
||||
int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE;
|
||||
|
||||
// Record one entry for the current UID.
|
||||
entry.rxBytes = 32;
|
||||
collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS,
|
||||
entry);
|
||||
|
||||
// Record one entry for another UID in this user.
|
||||
entry.rxBytes = 64;
|
||||
collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0,
|
||||
60 * MINUTE_IN_MILLIS, entry);
|
||||
|
||||
// Record one entry for the system UID.
|
||||
entry.rxBytes = 128;
|
||||
collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0,
|
||||
60 * MINUTE_IN_MILLIS, entry);
|
||||
|
||||
// Record one entry for a UID in a different user.
|
||||
entry.rxBytes = 256;
|
||||
collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0,
|
||||
60 * MINUTE_IN_MILLIS, entry);
|
||||
|
||||
// Verify the set of relevant UIDs for each access level.
|
||||
assertArrayEquals(new int[] { myUid },
|
||||
collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
|
||||
assertArrayEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
|
||||
collection.getRelevantUids(NetworkStatsAccess.Level.USER));
|
||||
assertArrayEquals(
|
||||
new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
|
||||
collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
|
||||
|
||||
// Verify security check in getHistory.
|
||||
assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, myUid, SET_DEFAULT,
|
||||
TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid));
|
||||
try {
|
||||
collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, otherUidInSameUser,
|
||||
SET_DEFAULT, TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid);
|
||||
fail("Should have thrown SecurityException for accessing different UID");
|
||||
} catch (SecurityException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
// Verify appropriate aggregation in getSummary.
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0,
|
||||
NetworkStatsAccess.Level.DEFAULT);
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0,
|
||||
NetworkStatsAccess.Level.USER);
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0,
|
||||
0, NetworkStatsAccess.Level.DEVICE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAugmentPlan() throws Exception {
|
||||
final File testFile =
|
||||
new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
|
||||
stageFile(R.raw.netstats_v1, testFile);
|
||||
|
||||
final NetworkStatsCollection emptyCollection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
|
||||
collection.readLegacyNetwork(testFile);
|
||||
|
||||
// We're in the future, but not that far off
|
||||
setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
|
||||
|
||||
// Test a bunch of plans that should result in no augmentation
|
||||
final List<SubscriptionPlan> plans = new ArrayList<>();
|
||||
|
||||
// No plan
|
||||
plans.add(null);
|
||||
// No usage anchor
|
||||
plans.add(SubscriptionPlan.Builder
|
||||
.createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")).build());
|
||||
// Usage anchor far in past
|
||||
plans.add(SubscriptionPlan.Builder
|
||||
.createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z"))
|
||||
.setDataUsage(1000L, TIME_A - DateUtils.YEAR_IN_MILLIS).build());
|
||||
// Usage anchor far in future
|
||||
plans.add(SubscriptionPlan.Builder
|
||||
.createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z"))
|
||||
.setDataUsage(1000L, TIME_A + DateUtils.YEAR_IN_MILLIS).build());
|
||||
// Usage anchor near but outside cycle
|
||||
plans.add(SubscriptionPlan.Builder
|
||||
.createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"),
|
||||
ZonedDateTime.parse("2012-01-09T15:00:00.00Z"))
|
||||
.setDataUsage(1000L, TIME_C).build());
|
||||
|
||||
for (SubscriptionPlan plan : plans) {
|
||||
int i;
|
||||
NetworkStatsHistory history;
|
||||
|
||||
// Empty collection should be untouched
|
||||
history = getHistory(emptyCollection, plan, TIME_A, TIME_C);
|
||||
assertEquals(0L, history.getTotalBytes());
|
||||
|
||||
// Normal collection should be untouched
|
||||
history = getHistory(collection, plan, TIME_A, TIME_C); i = 0;
|
||||
assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
|
||||
assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
|
||||
assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
|
||||
assertEntry(18322, 75, 15031, 75, history.getValues(i++, null));
|
||||
assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
|
||||
assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
|
||||
assertEntry(10747, 50, 16839, 55, history.getValues(i++, null));
|
||||
assertEntry(10747, 49, 16837, 54, history.getValues(i++, null));
|
||||
assertEntry(89191, 151, 18021, 140, history.getValues(i++, null));
|
||||
assertEntry(89190, 150, 18020, 139, history.getValues(i++, null));
|
||||
assertEntry(3821, 23, 4525, 26, history.getValues(i++, null));
|
||||
assertEntry(3820, 21, 4524, 26, history.getValues(i++, null));
|
||||
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
|
||||
assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
|
||||
assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
|
||||
assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
|
||||
assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
|
||||
assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
|
||||
assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
|
||||
assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
|
||||
assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
|
||||
assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
|
||||
assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
|
||||
assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
|
||||
assertEquals(history.size(), i);
|
||||
|
||||
// Slice from middle should be untouched
|
||||
history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
|
||||
TIME_B + HOUR_IN_MILLIS); i = 0;
|
||||
assertEntry(3821, 23, 4525, 26, history.getValues(i++, null));
|
||||
assertEntry(3820, 21, 4524, 26, history.getValues(i++, null));
|
||||
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
|
||||
assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
|
||||
assertEquals(history.size(), i);
|
||||
}
|
||||
|
||||
// Lower anchor in the middle of plan
|
||||
{
|
||||
int i;
|
||||
NetworkStatsHistory history;
|
||||
|
||||
final SubscriptionPlan plan = SubscriptionPlan.Builder
|
||||
.createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"),
|
||||
ZonedDateTime.parse("2012-01-09T15:00:00.00Z"))
|
||||
.setDataUsage(200000L, TIME_B).build();
|
||||
|
||||
// Empty collection should be augmented
|
||||
history = getHistory(emptyCollection, plan, TIME_A, TIME_C);
|
||||
assertEquals(200000L, history.getTotalBytes());
|
||||
|
||||
// Normal collection should be augmented
|
||||
history = getHistory(collection, plan, TIME_A, TIME_C); i = 0;
|
||||
assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
|
||||
assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
|
||||
assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
|
||||
assertEntry(18322, 75, 15031, 75, history.getValues(i++, null));
|
||||
assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
|
||||
assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
|
||||
// Cycle point; start data normalization
|
||||
assertEntry(7507, 0, 11763, 0, history.getValues(i++, null));
|
||||
assertEntry(7507, 0, 11762, 0, history.getValues(i++, null));
|
||||
assertEntry(62309, 0, 12589, 0, history.getValues(i++, null));
|
||||
assertEntry(62309, 0, 12588, 0, history.getValues(i++, null));
|
||||
assertEntry(2669, 0, 3161, 0, history.getValues(i++, null));
|
||||
assertEntry(2668, 0, 3160, 0, history.getValues(i++, null));
|
||||
// Anchor point; end data normalization
|
||||
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
|
||||
assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
|
||||
assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
|
||||
assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
|
||||
assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
|
||||
assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
|
||||
// Cycle point
|
||||
assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
|
||||
assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
|
||||
assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
|
||||
assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
|
||||
assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
|
||||
assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
|
||||
assertEquals(history.size(), i);
|
||||
|
||||
// Slice from middle should be augmented
|
||||
history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
|
||||
TIME_B + HOUR_IN_MILLIS); i = 0;
|
||||
assertEntry(2669, 0, 3161, 0, history.getValues(i++, null));
|
||||
assertEntry(2668, 0, 3160, 0, history.getValues(i++, null));
|
||||
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
|
||||
assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
|
||||
assertEquals(history.size(), i);
|
||||
}
|
||||
|
||||
// Higher anchor in the middle of plan
|
||||
{
|
||||
int i;
|
||||
NetworkStatsHistory history;
|
||||
|
||||
final SubscriptionPlan plan = SubscriptionPlan.Builder
|
||||
.createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"),
|
||||
ZonedDateTime.parse("2012-01-09T15:00:00.00Z"))
|
||||
.setDataUsage(400000L, TIME_B + MINUTE_IN_MILLIS).build();
|
||||
|
||||
// Empty collection should be augmented
|
||||
history = getHistory(emptyCollection, plan, TIME_A, TIME_C);
|
||||
assertEquals(400000L, history.getTotalBytes());
|
||||
|
||||
// Normal collection should be augmented
|
||||
history = getHistory(collection, plan, TIME_A, TIME_C); i = 0;
|
||||
assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
|
||||
assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
|
||||
assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
|
||||
assertEntry(18322, 75, 15031, 75, history.getValues(i++, null));
|
||||
assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
|
||||
assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
|
||||
// Cycle point; start data normalization
|
||||
assertEntry(15015, 0, 23527, 0, history.getValues(i++, null));
|
||||
assertEntry(15015, 0, 23524, 0, history.getValues(i++, null));
|
||||
assertEntry(124619, 0, 25179, 0, history.getValues(i++, null));
|
||||
assertEntry(124618, 0, 25177, 0, history.getValues(i++, null));
|
||||
assertEntry(5338, 0, 6322, 0, history.getValues(i++, null));
|
||||
assertEntry(5337, 0, 6320, 0, history.getValues(i++, null));
|
||||
// Anchor point; end data normalization
|
||||
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
|
||||
assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
|
||||
assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
|
||||
assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
|
||||
assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
|
||||
assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
|
||||
// Cycle point
|
||||
assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
|
||||
assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
|
||||
assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
|
||||
assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
|
||||
assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
|
||||
assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
|
||||
|
||||
// Slice from middle should be augmented
|
||||
history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
|
||||
TIME_B + HOUR_IN_MILLIS); i = 0;
|
||||
assertEntry(5338, 0, 6322, 0, history.getValues(i++, null));
|
||||
assertEntry(5337, 0, 6320, 0, history.getValues(i++, null));
|
||||
assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
|
||||
assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
|
||||
assertEquals(history.size(), i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAugmentPlanGigantic() throws Exception {
|
||||
// We're in the future, but not that far off
|
||||
setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
|
||||
|
||||
// Create a simple history with a ton of measured usage
|
||||
final NetworkStatsCollection large = new NetworkStatsCollection(HOUR_IN_MILLIS);
|
||||
final NetworkIdentitySet ident = new NetworkIdentitySet();
|
||||
ident.add(new NetworkIdentity(ConnectivityManager.TYPE_MOBILE, -1, TEST_IMSI, null,
|
||||
false, true, true, OEM_NONE));
|
||||
large.recordData(ident, UID_ALL, SET_ALL, TAG_NONE, TIME_A, TIME_B,
|
||||
new NetworkStats.Entry(12_730_893_164L, 1, 0, 0, 0));
|
||||
|
||||
// Verify untouched total
|
||||
assertEquals(12_730_893_164L, getHistory(large, null, TIME_A, TIME_C).getTotalBytes());
|
||||
|
||||
// Verify anchor that might cause overflows
|
||||
final SubscriptionPlan plan = SubscriptionPlan.Builder
|
||||
.createRecurringMonthly(ZonedDateTime.parse("2012-01-09T00:00:00.00Z"))
|
||||
.setDataUsage(4_939_212_390L, TIME_B).build();
|
||||
assertEquals(4_939_212_386L, getHistory(large, plan, TIME_A, TIME_C).getTotalBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRounding() throws Exception {
|
||||
final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS);
|
||||
|
||||
// Special values should remain unchanged
|
||||
for (long time : new long[] {
|
||||
Long.MIN_VALUE, Long.MAX_VALUE, SubscriptionPlan.TIME_UNKNOWN
|
||||
}) {
|
||||
assertEquals(time, coll.roundUp(time));
|
||||
assertEquals(time, coll.roundDown(time));
|
||||
}
|
||||
|
||||
assertEquals(TIME_A, coll.roundUp(TIME_A));
|
||||
assertEquals(TIME_A, coll.roundDown(TIME_A));
|
||||
|
||||
assertEquals(TIME_A + HOUR_IN_MILLIS, coll.roundUp(TIME_A + 1));
|
||||
assertEquals(TIME_A, coll.roundDown(TIME_A + 1));
|
||||
|
||||
assertEquals(TIME_A, coll.roundUp(TIME_A - 1));
|
||||
assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplySafeRational() {
|
||||
assertEquals(25, multiplySafeByRational(50, 1, 2));
|
||||
assertEquals(100, multiplySafeByRational(50, 2, 1));
|
||||
|
||||
assertEquals(-10, multiplySafeByRational(30, -1, 3));
|
||||
assertEquals(0, multiplySafeByRational(30, 0, 3));
|
||||
assertEquals(10, multiplySafeByRational(30, 1, 3));
|
||||
assertEquals(20, multiplySafeByRational(30, 2, 3));
|
||||
assertEquals(30, multiplySafeByRational(30, 3, 3));
|
||||
assertEquals(40, multiplySafeByRational(30, 4, 3));
|
||||
|
||||
assertEquals(100_000_000_000L,
|
||||
multiplySafeByRational(300_000_000_000L, 10_000_000_000L, 30_000_000_000L));
|
||||
assertEquals(100_000_000_010L,
|
||||
multiplySafeByRational(300_000_000_000L, 10_000_000_001L, 30_000_000_000L));
|
||||
assertEquals(823_202_048L,
|
||||
multiplySafeByRational(4_939_212_288L, 2_121_815_528L, 12_730_893_165L));
|
||||
|
||||
assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
|
||||
* testing purposes.
|
||||
*/
|
||||
private void stageFile(int rawId, File file) throws Exception {
|
||||
new File(file.getParent()).mkdirs();
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
|
||||
out = new FileOutputStream(file);
|
||||
Streams.copy(in, out);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(in);
|
||||
IoUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
private static NetworkStatsHistory getHistory(NetworkStatsCollection collection,
|
||||
SubscriptionPlan augmentPlan, long start, long end) {
|
||||
return collection.getHistory(buildTemplateMobileAll(TEST_IMSI), augmentPlan, UID_ALL,
|
||||
SET_ALL, TAG_NONE, FIELD_ALL, start, end, NetworkStatsAccess.Level.DEVICE, myUid());
|
||||
}
|
||||
|
||||
private static void assertSummaryTotal(NetworkStatsCollection collection,
|
||||
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
final NetworkStats.Entry actual = collection.getSummary(
|
||||
template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel, myUid())
|
||||
.getTotal(null);
|
||||
assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual);
|
||||
}
|
||||
|
||||
private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
|
||||
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
|
||||
final NetworkStats.Entry actual = collection.getSummary(
|
||||
template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE, myUid())
|
||||
.getTotalIncludingTags(null);
|
||||
assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual);
|
||||
}
|
||||
|
||||
private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets,
|
||||
NetworkStats.Entry actual) {
|
||||
assertEntry(new NetworkStats.Entry(rxBytes, rxPackets, txBytes, txPackets, 0L), actual);
|
||||
}
|
||||
|
||||
private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets,
|
||||
NetworkStatsHistory.Entry actual) {
|
||||
assertEntry(new NetworkStats.Entry(rxBytes, rxPackets, txBytes, txPackets, 0L), actual);
|
||||
}
|
||||
|
||||
private static void assertEntry(NetworkStats.Entry expected,
|
||||
NetworkStatsHistory.Entry actual) {
|
||||
assertEntry(expected, new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
|
||||
actual.txBytes, actual.txPackets, 0L));
|
||||
}
|
||||
|
||||
private static void assertEntry(NetworkStats.Entry expected,
|
||||
NetworkStats.Entry actual) {
|
||||
assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes);
|
||||
assertEquals("unexpected rxPackets", expected.rxPackets, actual.rxPackets);
|
||||
assertEquals("unexpected txBytes", expected.txBytes, actual.txBytes);
|
||||
assertEquals("unexpected txPackets", expected.txPackets, actual.txPackets);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.server.net;
|
||||
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
|
||||
import static android.net.NetworkStats.METERED_ALL;
|
||||
import static android.net.NetworkStats.METERED_NO;
|
||||
import static android.net.NetworkStats.ROAMING_ALL;
|
||||
import static android.net.NetworkStats.ROAMING_NO;
|
||||
import static android.net.NetworkStats.SET_ALL;
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.SET_FOREGROUND;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
|
||||
import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.UnderlyingNetworkInfo;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.frameworks.tests.net.R;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.io.Streams;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/** Tests for {@link NetworkStatsFactory}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
|
||||
private static final String CLAT_PREFIX = "v4-";
|
||||
|
||||
private File mTestProc;
|
||||
private NetworkStatsFactory mFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mTestProc = new File(InstrumentationRegistry.getContext().getFilesDir(), "proc");
|
||||
if (mTestProc.exists()) {
|
||||
IoUtils.deleteContents(mTestProc);
|
||||
}
|
||||
|
||||
// The libandroid_servers which have the native method is not available to
|
||||
// applications. So in order to have a test support native library, the native code
|
||||
// related to networkStatsFactory is compiled to a minimal native library and loaded here.
|
||||
System.loadLibrary("networkstatsfactorytestjni");
|
||||
mFactory = new NetworkStatsFactory(mTestProc, false);
|
||||
mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
mFactory = null;
|
||||
|
||||
if (mTestProc.exists()) {
|
||||
IoUtils.deleteContents(mTestProc);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkStatsDetail() throws Exception {
|
||||
final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
|
||||
|
||||
assertEquals(70, stats.size());
|
||||
assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
|
||||
assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
|
||||
assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
|
||||
assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
|
||||
assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnRewriteTrafficThroughItself() throws Exception {
|
||||
UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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
|
||||
|
||||
final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_rewrite_through_self);
|
||||
|
||||
assertValues(tunStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
|
||||
DEFAULT_NETWORK_ALL, 2000L, 200L, 1000L, 100L, 0);
|
||||
assertValues(tunStats, TUN_IFACE, UID_BLUE, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
|
||||
DEFAULT_NETWORK_ALL, 1000L, 100L, 500L, 50L, 0);
|
||||
assertValues(tunStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
|
||||
DEFAULT_NETWORK_ALL, 0L, 0L, 1600L, 160L, 0);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 260L, 26L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithClat() throws Exception {
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos = new UnderlyingNetworkInfo[] {
|
||||
createVpnInfo(new String[] {CLAT_PREFIX + TEST_IFACE})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
mFactory.noteStackedIface(CLAT_PREFIX + TEST_IFACE, TEST_IFACE);
|
||||
|
||||
// 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 sent 1650 bytes (150 packets), and received 3300 (300 packets) over v4-WiFi, and clat
|
||||
// added 20 bytes per packet of extra overhead
|
||||
//
|
||||
// For 1650 bytes sent over v4-WiFi, 4650 bytes were actually sent over WiFi, which is
|
||||
// expected to be split as follows:
|
||||
// UID_RED: 1000 bytes, 100 packets
|
||||
// UID_BLUE: 500 bytes, 50 packets
|
||||
// UID_VPN: 3150 bytes, 0 packets
|
||||
//
|
||||
// For 3300 bytes received over v4-WiFi, 9300 bytes were actually sent over WiFi, which is
|
||||
// expected to be split as follows:
|
||||
// UID_RED: 2000 bytes, 200 packets
|
||||
// UID_BLUE: 1000 bytes, 100 packets
|
||||
// UID_VPN: 6300 bytes, 0 packets
|
||||
final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_with_clat);
|
||||
|
||||
assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_RED, 2000L, 200L, 1000, 100L);
|
||||
assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
|
||||
assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_VPN, 6300L, 0L, 3150L, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithOneUnderlyingIface() throws Exception {
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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 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.
|
||||
final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 150L, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception {
|
||||
// WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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.
|
||||
final NetworkStats tunStats =
|
||||
parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_own_traffic);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 5800L, 500L, 6750L, 600L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithOneUnderlyingIface_withCompression() throws Exception {
|
||||
// WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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.
|
||||
// 3000 bytes (300 packets) were sent/received by UID_BLUE over VPN.
|
||||
// VPN sent/received 1000 bytes (100 packets) over WiFi.
|
||||
// Of 1000 bytes over WiFi, expect 250 bytes attributed UID_RED and 750 bytes to UID_BLUE,
|
||||
// with nothing attributed to UID_VPN for both rx/tx traffic.
|
||||
final NetworkStats tunStats =
|
||||
parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_compression);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 250L, 25L, 250L, 25L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_BLUE, 750L, 75L, 750L, 75L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithTwoUnderlyingIfaces_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.
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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.
|
||||
final NetworkStats tunStats =
|
||||
parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_duplication);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 500L, 50L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_BLUE, 500L, 50L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 1200L, 100L, 1200L, 100L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_RED, 500L, 50L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_BLUE, 500L, 50L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_VPN, 1200L, 100L, 1200L, 100L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcurrentVpns() throws Exception {
|
||||
// Assume two VPNs are connected on two different network interfaces. VPN1 is using
|
||||
// TEST_IFACE and VPN2 is using TEST_IFACE2.
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos = new UnderlyingNetworkInfo[] {
|
||||
createVpnInfo(TUN_IFACE, new String[] {TEST_IFACE}),
|
||||
createVpnInfo(TUN_IFACE2, new String[] {TEST_IFACE2})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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 VPN1.
|
||||
// 700 bytes (70 packets) were sent, and 3000 bytes (300 packets) were received by UID_RED
|
||||
// over VPN2.
|
||||
// 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
|
||||
// over VPN1.
|
||||
// 250 bytes (25 packets) were sent, and 500 bytes (50 packets) were received by UID_BLUE
|
||||
// over VPN2.
|
||||
// VPN1 sent 1650 bytes (150 packets), and received 3300 (300 packets) over TEST_IFACE.
|
||||
// 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.
|
||||
// VPN2 sent 1045 bytes (95 packets), and received 3850 (350 packets) over TEST_IFACE2.
|
||||
// Of 1045 bytes sent over Cell, expect 700 bytes attributed to UID_RED, 250 bytes
|
||||
// attributed to UID_BLUE, and 95 bytes attributed to UID_VPN.
|
||||
// Of 3850 bytes received over Cell, expect 3000 bytes attributed to UID_RED, 500 bytes
|
||||
// attributed to UID_BLUE, and 350 bytes attributed to UID_VPN.
|
||||
final NetworkStats tunStats =
|
||||
parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_two_vpn);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_RED, 3000L, 300L, 700L, 70L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_BLUE, 500L, 50L, 250L, 25L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 150L, 0L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_VPN, 350L, 0L, 95L, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithTwoUnderlyingIfaces_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.
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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 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.
|
||||
final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 300L, 30L, 600L, 60L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 30L, 0L, 60L, 0L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 400L, 40L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_VPN, 20L, 0L, 40L, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithTwoUnderlyingIfaces_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.
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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).
|
||||
final NetworkStats tunStats =
|
||||
parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split_compression);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 600L, 60L, 600L, 60L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 200L, 20L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_VPN, 0L, 0L, 0L, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnWithIncorrectUnderlyingIface() throws Exception {
|
||||
// WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2),
|
||||
// but has declared only WiFi (TEST_IFACE) in its underlying network set.
|
||||
final UnderlyingNetworkInfo[] underlyingNetworkInfos =
|
||||
new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
|
||||
mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
|
||||
|
||||
// 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 1100 bytes (100 packets) over Cell.
|
||||
// Of 1100 bytes over Cell, expect all of it attributed to UID_VPN for both rx/tx traffic.
|
||||
final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_incorrect_iface);
|
||||
|
||||
assertValues(tunStats, TEST_IFACE, UID_RED, 0L, 0L, 0L, 0L);
|
||||
assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_RED, 0L, 0L, 0L, 0L);
|
||||
assertValues(tunStats, TEST_IFACE2, UID_VPN, 1100L, 100L, 1100L, 100L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKernelTags() throws Exception {
|
||||
assertEquals(0, kernelToTag("0x0000000000000000"));
|
||||
assertEquals(0x32, kernelToTag("0x0000003200000000"));
|
||||
assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
|
||||
assertEquals(0, kernelToTag("0x0000000000000000"));
|
||||
assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
|
||||
|
||||
assertEquals(0, kernelToTag("0x0"));
|
||||
assertEquals(0, kernelToTag("0xf00d"));
|
||||
assertEquals(1, kernelToTag("0x100000000"));
|
||||
assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
|
||||
assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkStatsWithSet() throws Exception {
|
||||
final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
|
||||
assertEquals(70, stats.size());
|
||||
assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L,
|
||||
676L);
|
||||
assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkStatsSingle() throws Exception {
|
||||
stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all"));
|
||||
|
||||
final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
|
||||
assertEquals(6, stats.size());
|
||||
assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
|
||||
assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
|
||||
assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkStatsXt() throws Exception {
|
||||
stageFile(R.raw.xt_qtaguid_iface_fmt_typical, file("net/xt_qtaguid/iface_stat_fmt"));
|
||||
|
||||
final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
|
||||
assertEquals(3, stats.size());
|
||||
assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
|
||||
assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L,
|
||||
2468L);
|
||||
assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClatAccountingSimple() throws Exception {
|
||||
mFactory.noteStackedIface("v4-wlan0", "wlan0");
|
||||
|
||||
// xt_qtaguid_with_clat_simple is a synthetic file that simulates
|
||||
// - 213 received 464xlat packets of size 200 bytes
|
||||
// - 41 sent 464xlat packets of size 100 bytes
|
||||
// - no other traffic on base interface for root uid.
|
||||
NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_simple);
|
||||
assertEquals(3, stats.size());
|
||||
|
||||
assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 46860L, 4920L);
|
||||
assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClatAccounting() throws Exception {
|
||||
mFactory.noteStackedIface("v4-wlan0", "wlan0");
|
||||
|
||||
NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat);
|
||||
assertEquals(42, stats.size());
|
||||
|
||||
assertStatsEntry(stats, "v4-wlan0", 0, SET_DEFAULT, 0x0, 356L, 276L);
|
||||
assertStatsEntry(stats, "v4-wlan0", 1000, SET_DEFAULT, 0x0, 30812L, 2310L);
|
||||
assertStatsEntry(stats, "v4-wlan0", 10102, SET_DEFAULT, 0x0, 10022L, 3330L);
|
||||
assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 9532772L, 254112L);
|
||||
assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L);
|
||||
assertStatsEntry(stats, "wlan0", 1000, SET_DEFAULT, 0x0, 6126L, 2013L);
|
||||
assertStatsEntry(stats, "wlan0", 10013, SET_DEFAULT, 0x0, 0L, 144L);
|
||||
assertStatsEntry(stats, "wlan0", 10018, SET_DEFAULT, 0x0, 5980263L, 167667L);
|
||||
assertStatsEntry(stats, "wlan0", 10060, SET_DEFAULT, 0x0, 134356L, 8705L);
|
||||
assertStatsEntry(stats, "wlan0", 10079, SET_DEFAULT, 0x0, 10926L, 1507L);
|
||||
assertStatsEntry(stats, "wlan0", 10102, SET_DEFAULT, 0x0, 25038L, 8245L);
|
||||
assertStatsEntry(stats, "wlan0", 10103, SET_DEFAULT, 0x0, 0L, 192L);
|
||||
assertStatsEntry(stats, "dummy0", 0, SET_DEFAULT, 0x0, 0L, 168L);
|
||||
assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L);
|
||||
|
||||
assertNoStatsEntry(stats, "wlan0", 1029, SET_DEFAULT, 0x0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClatAccounting100MBDownload() throws Exception {
|
||||
// Downloading 100mb from an ipv4 only destination in a foreground activity
|
||||
|
||||
long appRxBytesBefore = 328684029L;
|
||||
long appRxBytesAfter = 439237478L;
|
||||
assertEquals("App traffic should be ~100MB", 110553449, appRxBytesAfter - appRxBytesBefore);
|
||||
|
||||
long rootRxBytes = 330187296L;
|
||||
|
||||
mFactory.noteStackedIface("v4-wlan0", "wlan0");
|
||||
NetworkStats stats;
|
||||
|
||||
// Stats snapshot before the download
|
||||
stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_before);
|
||||
assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesBefore, 5199872L);
|
||||
assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytes, 0L);
|
||||
|
||||
// Stats snapshot after the download
|
||||
stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after);
|
||||
assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L);
|
||||
assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytes, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
|
||||
* testing purposes.
|
||||
*/
|
||||
private void stageFile(int rawId, File file) throws Exception {
|
||||
new File(file.getParent()).mkdirs();
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
|
||||
out = new FileOutputStream(file);
|
||||
Streams.copy(in, out);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(in);
|
||||
IoUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
private void stageLong(long value, File file) throws Exception {
|
||||
new File(file.getParent()).mkdirs();
|
||||
FileWriter out = null;
|
||||
try {
|
||||
out = new FileWriter(file);
|
||||
out.write(Long.toString(value));
|
||||
} finally {
|
||||
IoUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
private File file(String path) throws Exception {
|
||||
return new File(mTestProc, path);
|
||||
}
|
||||
|
||||
private NetworkStats parseDetailedStats(int resourceId) throws Exception {
|
||||
stageFile(resourceId, file("net/xt_qtaguid/stats"));
|
||||
return mFactory.readNetworkStatsDetail();
|
||||
}
|
||||
|
||||
private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
|
||||
int tag, long rxBytes, long txBytes) {
|
||||
final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_NO);
|
||||
if (i < 0) {
|
||||
fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
|
||||
iface, uid, set, tag));
|
||||
}
|
||||
final NetworkStats.Entry entry = stats.getValues(i, null);
|
||||
assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
|
||||
assertEquals("unexpected txBytes", txBytes, entry.txBytes);
|
||||
}
|
||||
|
||||
private static void assertNoStatsEntry(NetworkStats stats, String iface, int uid, int set,
|
||||
int tag) {
|
||||
final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_NO);
|
||||
if (i >= 0) {
|
||||
fail("unexpected NetworkStats entry at " + i);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
|
||||
int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
|
||||
assertStatsEntry(stats, iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
|
||||
rxBytes, rxPackets, txBytes, txPackets);
|
||||
}
|
||||
|
||||
private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
|
||||
int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
|
||||
long txBytes, long txPackets) {
|
||||
final int i = stats.findIndex(iface, uid, set, tag, metered, roaming, defaultNetwork);
|
||||
|
||||
if (i < 0) {
|
||||
fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d, metered:"
|
||||
+ " %d, roaming: %d, defaultNetwork: %d)",
|
||||
iface, uid, set, tag, metered, roaming, defaultNetwork));
|
||||
}
|
||||
final NetworkStats.Entry entry = stats.getValues(i, null);
|
||||
assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
|
||||
assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
|
||||
assertEquals("unexpected txBytes", txBytes, entry.txBytes);
|
||||
assertEquals("unexpected txPackets", txPackets, entry.txPackets);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.server.net;
|
||||
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||
import static android.net.NetworkIdentity.OEM_NONE;
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
|
||||
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.NetworkTemplate.buildTemplateMobileAll;
|
||||
import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
|
||||
import static android.net.TrafficStats.MB_IN_BYTES;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
|
||||
import android.app.usage.NetworkStatsManager;
|
||||
import android.net.DataUsageRequest;
|
||||
import android.net.NetworkIdentity;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Messenger;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
|
||||
import com.android.testutils.HandlerUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Tests for {@link NetworkStatsObservers}.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetworkStatsObserversTest {
|
||||
private static final String TEST_IFACE = "test0";
|
||||
private static final String TEST_IFACE2 = "test1";
|
||||
private static final long TEST_START = 1194220800000L;
|
||||
|
||||
private static final String IMSI_1 = "310004";
|
||||
private static final String IMSI_2 = "310260";
|
||||
private static final String TEST_SSID = "AndroidAP";
|
||||
|
||||
private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
|
||||
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
|
||||
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
|
||||
|
||||
private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
|
||||
private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
|
||||
private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
|
||||
private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
|
||||
|
||||
private static final long WAIT_TIMEOUT_MS = 500;
|
||||
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
|
||||
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
|
||||
private static final int INVALID_TYPE = -1;
|
||||
|
||||
private long mElapsedRealtime;
|
||||
|
||||
private HandlerThread mObserverHandlerThread;
|
||||
private Handler mObserverNoopHandler;
|
||||
|
||||
private LatchedHandler mHandler;
|
||||
|
||||
private NetworkStatsObservers mStatsObservers;
|
||||
private Messenger mMessenger;
|
||||
private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
|
||||
private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
|
||||
|
||||
@Mock private IBinder mockBinder;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mObserverHandlerThread = new HandlerThread("HandlerThread");
|
||||
mObserverHandlerThread.start();
|
||||
final Looper observerLooper = mObserverHandlerThread.getLooper();
|
||||
mStatsObservers = new NetworkStatsObservers() {
|
||||
@Override
|
||||
protected Looper getHandlerLooperLocked() {
|
||||
return observerLooper;
|
||||
}
|
||||
};
|
||||
|
||||
mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
|
||||
mMessenger = new Messenger(mHandler);
|
||||
|
||||
mActiveIfaces = new ArrayMap<>();
|
||||
mActiveUidIfaces = new ArrayMap<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
|
||||
long thresholdTooLowBytes = 1L;
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateWifi, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegister_highThreshold_accepted() throws Exception {
|
||||
long highThresholdBytes = 2 * THRESHOLD_BYTES;
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateWifi, request.template));
|
||||
assertEquals(highThresholdBytes, request.thresholdInBytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegister_twoRequests_twoIds() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request1.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateWifi, request1.template));
|
||||
assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
|
||||
|
||||
DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request2.requestId > request1.requestId);
|
||||
assertTrue(Objects.equals(sTemplateWifi, request2.template));
|
||||
assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregister_unknownRequest_noop() throws Exception {
|
||||
DataUsageRequest unknownRequest = new DataUsageRequest(
|
||||
123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
|
||||
|
||||
mStatsObservers.unregister(unknownRequest, UID_RED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregister_knownRequest_releasesCaller() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
|
||||
|
||||
mStatsObservers.unregister(request, Process.SYSTEM_UID);
|
||||
waitForObserverToIdle();
|
||||
|
||||
Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
UID_RED, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
|
||||
|
||||
mStatsObservers.unregister(request, UID_BLUE);
|
||||
waitForObserverToIdle();
|
||||
|
||||
Mockito.verifyZeroInteractions(mockBinder);
|
||||
}
|
||||
|
||||
private NetworkIdentitySet makeTestIdentSet() {
|
||||
NetworkIdentitySet identSet = new NetworkIdentitySet();
|
||||
identSet.add(new NetworkIdentity(
|
||||
TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
|
||||
IMSI_1, null /* networkId */, false /* roaming */, true /* metered */,
|
||||
true /* defaultNetwork */, OEM_NONE));
|
||||
return identSet;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
|
||||
NetworkIdentitySet identSet = makeTestIdentSet();
|
||||
mActiveIfaces.put(TEST_IFACE, identSet);
|
||||
|
||||
// Baseline
|
||||
NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
|
||||
NetworkStats uidSnapshot = null;
|
||||
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
waitForObserverToIdle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
|
||||
NetworkIdentitySet identSet = makeTestIdentSet();
|
||||
mActiveIfaces.put(TEST_IFACE, identSet);
|
||||
|
||||
// Baseline
|
||||
NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
|
||||
NetworkStats uidSnapshot = null;
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
|
||||
// Delta
|
||||
xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
waitForObserverToIdle();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateStats_deviceAccess_notifies() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
|
||||
NetworkIdentitySet identSet = makeTestIdentSet();
|
||||
mActiveIfaces.put(TEST_IFACE, identSet);
|
||||
|
||||
// Baseline
|
||||
NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
|
||||
NetworkStats uidSnapshot = null;
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
|
||||
// Delta
|
||||
xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L,
|
||||
BASE_BYTES + THRESHOLD_BYTES, 22L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
waitForObserverToIdle();
|
||||
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStats_defaultAccess_notifiesSameUid() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
UID_RED, NetworkStatsAccess.Level.DEFAULT);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
|
||||
NetworkIdentitySet identSet = makeTestIdentSet();
|
||||
mActiveUidIfaces.put(TEST_IFACE, identSet);
|
||||
|
||||
// Baseline
|
||||
NetworkStats xtSnapshot = null;
|
||||
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
|
||||
// Delta
|
||||
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
|
||||
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
waitForObserverToIdle();
|
||||
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStats_defaultAccess_usageOtherUid_doesNotNotify() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
|
||||
NetworkIdentitySet identSet = makeTestIdentSet();
|
||||
mActiveUidIfaces.put(TEST_IFACE, identSet);
|
||||
|
||||
// Baseline
|
||||
NetworkStats xtSnapshot = null;
|
||||
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
|
||||
// Delta
|
||||
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
|
||||
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
waitForObserverToIdle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
UID_BLUE, NetworkStatsAccess.Level.USER);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
|
||||
NetworkIdentitySet identSet = makeTestIdentSet();
|
||||
mActiveUidIfaces.put(TEST_IFACE, identSet);
|
||||
|
||||
// Baseline
|
||||
NetworkStats xtSnapshot = null;
|
||||
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
|
||||
// Delta
|
||||
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
|
||||
DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L,
|
||||
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
waitForObserverToIdle();
|
||||
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
|
||||
DataUsageRequest inputRequest = new DataUsageRequest(
|
||||
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
|
||||
|
||||
DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
|
||||
UID_RED, NetworkStatsAccess.Level.USER);
|
||||
assertTrue(request.requestId > 0);
|
||||
assertTrue(Objects.equals(sTemplateImsi1, request.template));
|
||||
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
|
||||
|
||||
NetworkIdentitySet identSet = makeTestIdentSet();
|
||||
mActiveUidIfaces.put(TEST_IFACE, identSet);
|
||||
|
||||
// Baseline
|
||||
NetworkStats xtSnapshot = null;
|
||||
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
|
||||
ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
|
||||
// Delta
|
||||
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
|
||||
.insertEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
|
||||
ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
|
||||
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
|
||||
mStatsObservers.updateStats(
|
||||
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
|
||||
waitForObserverToIdle();
|
||||
}
|
||||
|
||||
private void waitForObserverToIdle() {
|
||||
HandlerUtils.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
|
||||
HandlerUtils.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
1767
tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
Normal file
1767
tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* 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.server.net;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
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.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.test.TestLooper;
|
||||
import android.telephony.NetworkRegistrationInfo;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.ServiceState;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.server.net.NetworkStatsSubscriptionsMonitor.RatTypeListener;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class NetworkStatsSubscriptionsMonitorTest {
|
||||
private static final int TEST_SUBID1 = 3;
|
||||
private static final int TEST_SUBID2 = 5;
|
||||
private static final String TEST_IMSI1 = "466921234567890";
|
||||
private static final String TEST_IMSI2 = "466920987654321";
|
||||
private static final String TEST_IMSI3 = "466929999999999";
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private SubscriptionManager mSubscriptionManager;
|
||||
@Mock private TelephonyManager mTelephonyManager;
|
||||
@Mock private NetworkStatsSubscriptionsMonitor.Delegate mDelegate;
|
||||
private final List<Integer> mTestSubList = new ArrayList<>();
|
||||
|
||||
private final Executor mExecutor = Executors.newSingleThreadExecutor();
|
||||
private NetworkStatsSubscriptionsMonitor mMonitor;
|
||||
private TestLooper mTestLooper = new TestLooper();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
|
||||
|
||||
when(mContext.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
|
||||
.thenReturn(mSubscriptionManager);
|
||||
when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE)))
|
||||
.thenReturn(mTelephonyManager);
|
||||
|
||||
mMonitor = new NetworkStatsSubscriptionsMonitor(mContext, mTestLooper.getLooper(),
|
||||
mExecutor, mDelegate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartStop() {
|
||||
// Verify that addOnSubscriptionsChangedListener() is never called before start().
|
||||
verify(mSubscriptionManager, never())
|
||||
.addOnSubscriptionsChangedListener(mExecutor, mMonitor);
|
||||
mMonitor.start();
|
||||
verify(mSubscriptionManager).addOnSubscriptionsChangedListener(mExecutor, mMonitor);
|
||||
|
||||
// Verify that removeOnSubscriptionsChangedListener() is never called before stop()
|
||||
verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(mMonitor);
|
||||
mMonitor.stop();
|
||||
verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(mMonitor);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static int[] convertArrayListToIntArray(@NonNull List<Integer> arrayList) {
|
||||
final int[] list = new int[arrayList.size()];
|
||||
for (int i = 0; i < arrayList.size(); i++) {
|
||||
list[i] = arrayList.get(i);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void setRatTypeForSub(List<RatTypeListener> listeners,
|
||||
int subId, int type) {
|
||||
final ServiceState serviceState = mock(ServiceState.class);
|
||||
when(serviceState.getDataNetworkType()).thenReturn(type);
|
||||
final RatTypeListener match = CollectionUtils
|
||||
.find(listeners, it -> it.getSubId() == subId);
|
||||
if (match == null) {
|
||||
fail("Could not find listener with subId: " + subId);
|
||||
}
|
||||
match.onServiceStateChanged(serviceState);
|
||||
}
|
||||
|
||||
private void addTestSub(int subId, String subscriberId) {
|
||||
// add SubId to TestSubList.
|
||||
if (mTestSubList.contains(subId)) fail("The subscriber list already contains this ID");
|
||||
|
||||
mTestSubList.add(subId);
|
||||
|
||||
final int[] subList = convertArrayListToIntArray(mTestSubList);
|
||||
when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList);
|
||||
when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId);
|
||||
mMonitor.onSubscriptionsChanged();
|
||||
}
|
||||
|
||||
private void updateSubscriberIdForTestSub(int subId, @Nullable final String subscriberId) {
|
||||
when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId);
|
||||
mMonitor.onSubscriptionsChanged();
|
||||
}
|
||||
|
||||
private void removeTestSub(int subId) {
|
||||
// Remove subId from TestSubList.
|
||||
mTestSubList.removeIf(it -> it == subId);
|
||||
final int[] subList = convertArrayListToIntArray(mTestSubList);
|
||||
when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList);
|
||||
mMonitor.onSubscriptionsChanged();
|
||||
}
|
||||
|
||||
private void assertRatTypeChangedForSub(String subscriberId, int ratType) {
|
||||
assertEquals(ratType, mMonitor.getRatTypeForSubscriberId(subscriberId));
|
||||
final ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
// Verify callback with the subscriberId and the RAT type should be as expected.
|
||||
// It will fail if get a callback with an unexpected RAT type.
|
||||
verify(mDelegate).onCollapsedRatTypeChanged(eq(subscriberId), typeCaptor.capture());
|
||||
final int type = typeCaptor.getValue();
|
||||
assertEquals(ratType, type);
|
||||
}
|
||||
|
||||
private void assertRatTypeNotChangedForSub(String subscriberId, int ratType) {
|
||||
assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType);
|
||||
// Should never get callback with any RAT type.
|
||||
verify(mDelegate, never()).onCollapsedRatTypeChanged(eq(subscriberId), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubChangedAndRatTypeChanged() {
|
||||
final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
|
||||
ArgumentCaptor.forClass(RatTypeListener.class);
|
||||
|
||||
mMonitor.start();
|
||||
// Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
|
||||
// before changing RAT type.
|
||||
addTestSub(TEST_SUBID1, TEST_IMSI1);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
|
||||
// Insert sim2.
|
||||
addTestSub(TEST_SUBID2, TEST_IMSI2);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
verify(mTelephonyManager, times(2)).listen(ratTypeListenerCaptor.capture(),
|
||||
eq(PhoneStateListener.LISTEN_SERVICE_STATE));
|
||||
reset(mDelegate);
|
||||
|
||||
// Set RAT type of sim1 to UMTS.
|
||||
// Verify RAT type of sim1 after subscription gets onCollapsedRatTypeChanged() callback
|
||||
// and others remain untouched.
|
||||
setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
|
||||
TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
reset(mDelegate);
|
||||
|
||||
// Set RAT type of sim2 to LTE.
|
||||
// Verify RAT type of sim2 after subscription gets onCollapsedRatTypeChanged() callback
|
||||
// and others remain untouched.
|
||||
setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID2,
|
||||
TelephonyManager.NETWORK_TYPE_LTE);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_LTE);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
reset(mDelegate);
|
||||
|
||||
// Remove sim2 and verify that callbacks are fired and RAT type is correct for sim2.
|
||||
// while the other two remain untouched.
|
||||
removeTestSub(TEST_SUBID2);
|
||||
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
reset(mDelegate);
|
||||
|
||||
// Set RAT type of sim1 to UNKNOWN. Then stop monitoring subscription changes
|
||||
// and verify that the listener for sim1 is removed.
|
||||
setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
|
||||
TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
reset(mDelegate);
|
||||
|
||||
mMonitor.stop();
|
||||
verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test5g() {
|
||||
mMonitor.start();
|
||||
// Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
|
||||
// before changing RAT type. Also capture listener for later use.
|
||||
addTestSub(TEST_SUBID1, TEST_IMSI1);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
|
||||
ArgumentCaptor.forClass(RatTypeListener.class);
|
||||
verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
|
||||
eq(PhoneStateListener.LISTEN_SERVICE_STATE));
|
||||
final RatTypeListener listener = CollectionUtils
|
||||
.find(ratTypeListenerCaptor.getAllValues(), it -> it.getSubId() == TEST_SUBID1);
|
||||
assertNotNull(listener);
|
||||
|
||||
// Set RAT type to 5G NSA (non-standalone) mode, verify the monitor outputs
|
||||
// NETWORK_TYPE_5G_NSA.
|
||||
final ServiceState serviceState = mock(ServiceState.class);
|
||||
when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
|
||||
when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
|
||||
listener.onServiceStateChanged(serviceState);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, NetworkTemplate.NETWORK_TYPE_5G_NSA);
|
||||
reset(mDelegate);
|
||||
|
||||
// Set RAT type to LTE without NR connected, the RAT type should be downgraded to LTE.
|
||||
when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE);
|
||||
listener.onServiceStateChanged(serviceState);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
|
||||
reset(mDelegate);
|
||||
|
||||
// Verify NR connected with other RAT type does not take effect.
|
||||
when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
|
||||
listener.onServiceStateChanged(serviceState);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
reset(mDelegate);
|
||||
|
||||
// Set RAT type to 5G standalone mode, the RAT type should be NR.
|
||||
setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
|
||||
TelephonyManager.NETWORK_TYPE_NR);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
|
||||
reset(mDelegate);
|
||||
|
||||
// Set NR state to none in standalone mode does not change anything.
|
||||
when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_NR);
|
||||
when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE);
|
||||
listener.onServiceStateChanged(serviceState);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriberIdUnavailable() {
|
||||
final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
|
||||
ArgumentCaptor.forClass(RatTypeListener.class);
|
||||
|
||||
mMonitor.start();
|
||||
// Insert sim1, set subscriberId to null which is normal in SIM PIN locked case.
|
||||
// Verify RAT type is NETWORK_TYPE_UNKNOWN and service will not perform listener
|
||||
// registration.
|
||||
addTestSub(TEST_SUBID1, null);
|
||||
verify(mTelephonyManager, never()).listen(any(), anyInt());
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
|
||||
// Set IMSI for sim1, verify the listener will be registered.
|
||||
updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1);
|
||||
verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
|
||||
eq(PhoneStateListener.LISTEN_SERVICE_STATE));
|
||||
reset(mTelephonyManager);
|
||||
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
|
||||
|
||||
// Set RAT type of sim1 to UMTS. Verify RAT type of sim1 is changed.
|
||||
setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
|
||||
TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
reset(mDelegate);
|
||||
|
||||
// Set IMSI to null again to simulate somehow IMSI is not available, such as
|
||||
// modem crash. Verify service should unregister listener.
|
||||
updateSubscriberIdForTestSub(TEST_SUBID1, null);
|
||||
verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
|
||||
eq(PhoneStateListener.LISTEN_NONE));
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
reset(mDelegate);
|
||||
clearInvocations(mTelephonyManager);
|
||||
|
||||
// Simulate somehow IMSI is back. Verify service will register with
|
||||
// another listener and fire callback accordingly.
|
||||
final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
|
||||
ArgumentCaptor.forClass(RatTypeListener.class);
|
||||
updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1);
|
||||
verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
|
||||
eq(PhoneStateListener.LISTEN_SERVICE_STATE));
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
reset(mDelegate);
|
||||
clearInvocations(mTelephonyManager);
|
||||
|
||||
// Set RAT type of sim1 to LTE. Verify RAT type of sim1 still works.
|
||||
setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
|
||||
TelephonyManager.NETWORK_TYPE_LTE);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
|
||||
reset(mDelegate);
|
||||
|
||||
mMonitor.stop();
|
||||
verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor2.getValue()),
|
||||
eq(PhoneStateListener.LISTEN_NONE));
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that when IMSI suddenly changed for a given subId, the service will register a new
|
||||
* listener and unregister the old one, and report changes on updated IMSI. This is for modem
|
||||
* feature that may be enabled for certain carrier, which changes to use a different IMSI while
|
||||
* roaming on certain networks for multi-IMSI SIM cards, but the subId stays the same.
|
||||
*/
|
||||
@Test
|
||||
public void testSubscriberIdChanged() {
|
||||
mMonitor.start();
|
||||
// Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
|
||||
// before changing RAT type.
|
||||
addTestSub(TEST_SUBID1, TEST_IMSI1);
|
||||
final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
|
||||
ArgumentCaptor.forClass(RatTypeListener.class);
|
||||
verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
|
||||
eq(PhoneStateListener.LISTEN_SERVICE_STATE));
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
|
||||
// Set RAT type of sim1 to UMTS.
|
||||
// Verify RAT type of sim1 changes accordingly.
|
||||
setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
|
||||
TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
reset(mDelegate);
|
||||
clearInvocations(mTelephonyManager);
|
||||
|
||||
// Simulate IMSI of sim1 changed to IMSI2. Verify the service will register with
|
||||
// another listener and remove the old one. The RAT type of new IMSI stays at
|
||||
// NETWORK_TYPE_UNKNOWN until received initial callback from telephony.
|
||||
final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
|
||||
ArgumentCaptor.forClass(RatTypeListener.class);
|
||||
updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI2);
|
||||
verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
|
||||
eq(PhoneStateListener.LISTEN_SERVICE_STATE));
|
||||
verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
|
||||
eq(PhoneStateListener.LISTEN_NONE));
|
||||
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
reset(mDelegate);
|
||||
|
||||
// Set RAT type of sim1 to UMTS for new listener to simulate the initial callback received
|
||||
// from telephony after registration. Verify RAT type of sim1 changes with IMSI2
|
||||
// accordingly.
|
||||
setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
|
||||
TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UMTS);
|
||||
reset(mDelegate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.server.net.ipmemorystore;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.net.ipmemorystore.NetworkAttributes;
|
||||
import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Unit tests for {@link NetworkAttributes}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NetworkAttributesTest {
|
||||
private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
|
||||
private static final float EPSILON = 0.0001f;
|
||||
|
||||
// This is running two tests to make sure the total weight is the sum of all weights. To be
|
||||
// sure this is not fireproof, but you'd kind of need to do it on purpose to pass.
|
||||
@Test
|
||||
public void testTotalWeight() throws IllegalAccessException, UnknownHostException {
|
||||
// Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_
|
||||
float sum = 0f;
|
||||
final Field[] fieldList = NetworkAttributes.class.getDeclaredFields();
|
||||
for (final Field field : fieldList) {
|
||||
if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue;
|
||||
field.setAccessible(true);
|
||||
sum += (float) field.get(null);
|
||||
}
|
||||
assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON);
|
||||
|
||||
final IPv6ProvisioningLossQuirk ipv6ProvisioningLossQuirk =
|
||||
new IPv6ProvisioningLossQuirk(3, System.currentTimeMillis() + 7_200_000);
|
||||
// Use directly the constructor with all attributes, and make sure that when compared
|
||||
// to itself the score is a clean 1.0f.
|
||||
final NetworkAttributes na =
|
||||
new NetworkAttributes(
|
||||
(Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
|
||||
System.currentTimeMillis() + 7_200_000,
|
||||
"some hint",
|
||||
Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
|
||||
Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
|
||||
98, ipv6ProvisioningLossQuirk);
|
||||
assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user