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:
Remi NGUYEN VAN
2021-05-11 13:37:06 +00:00
parent 87deb0b38a
commit 5ed250d90a
145 changed files with 625 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}

View 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());
}
}

View 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)
}
}

View 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())
}
}

View File

@@ -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);
}
}
}

View 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);
}
}
}

View File

@@ -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);
}
}

View 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 })
}
}

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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))
}
}

View File

@@ -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 });
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}

View 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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}

View File

@@ -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);
}
}