Merge "[ipsec-qtaguid] Tag sockets upon creation of encap sockets"
am: d0ac50bcad
Change-Id: If2f71108d0c3d5742ee39b041f5fa886aac820cb
This commit is contained in:
@@ -253,6 +253,13 @@ package android.net {
|
||||
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
|
||||
}
|
||||
|
||||
public class TrafficStats {
|
||||
method public static long getLoopbackRxBytes();
|
||||
method public static long getLoopbackRxPackets();
|
||||
method public static long getLoopbackTxBytes();
|
||||
method public static long getLoopbackTxPackets();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.os {
|
||||
|
||||
@@ -19,6 +19,7 @@ package android.net;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.DownloadManager;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.app.usage.NetworkStatsManager;
|
||||
@@ -154,6 +155,8 @@ public class TrafficStats {
|
||||
|
||||
private static Object sProfilingLock = new Object();
|
||||
|
||||
private static final String LOOPBACK_IFACE = "lo";
|
||||
|
||||
/**
|
||||
* Set active tag to use when accounting {@link Socket} traffic originating
|
||||
* from the current thread. Only one active tag per thread is supported.
|
||||
@@ -542,6 +545,30 @@ public class TrafficStats {
|
||||
return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@TestApi
|
||||
public static long getLoopbackTxPackets() {
|
||||
return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_PACKETS);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@TestApi
|
||||
public static long getLoopbackRxPackets() {
|
||||
return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_PACKETS);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@TestApi
|
||||
public static long getLoopbackTxBytes() {
|
||||
return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_BYTES);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@TestApi
|
||||
public static long getLoopbackRxBytes() {
|
||||
return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return number of packets transmitted since device boot. Counts packets
|
||||
* across all network interfaces, and always increases monotonically since
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.net.IpSecTransform;
|
||||
import android.net.IpSecTransformResponse;
|
||||
import android.net.IpSecUdpEncapResponse;
|
||||
import android.net.NetworkUtils;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.util.NetdService;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
@@ -120,6 +121,7 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
}
|
||||
|
||||
private final IpSecServiceConfiguration mSrvConfig;
|
||||
final UidFdTagger mUidFdTagger;
|
||||
|
||||
/**
|
||||
* Interface for user-reference and kernel-resource cleanup.
|
||||
@@ -762,8 +764,23 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public IpSecService(Context context, IpSecServiceConfiguration config) {
|
||||
this(context, config, (fd, uid) -> {
|
||||
try{
|
||||
TrafficStats.setThreadStatsUid(uid);
|
||||
TrafficStats.tagFileDescriptor(fd);
|
||||
} finally {
|
||||
TrafficStats.clearThreadStatsUid();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public IpSecService(
|
||||
Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
|
||||
mContext = context;
|
||||
mSrvConfig = config;
|
||||
mUidFdTagger = uidFdTagger;
|
||||
}
|
||||
|
||||
public void systemReady() {
|
||||
@@ -924,6 +941,26 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface to do traffic tagging of given sockets to UIDs.
|
||||
*
|
||||
* <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
|
||||
* sockets are billed to the UID that the UDP encap socket was created on behalf of.
|
||||
*
|
||||
* <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
|
||||
* methods that cannot be easily mocked/tested.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public interface UidFdTagger {
|
||||
/**
|
||||
* Sets socket tag to assign all traffic to the provided UID.
|
||||
*
|
||||
* <p>Since the socket is created on behalf of an unprivileged application, all traffic
|
||||
* should be accounted to the UID of the unprivileged application.
|
||||
*/
|
||||
public void tag(FileDescriptor fd, int uid) throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a socket via the system server and bind it to the specified port (random if port=0).
|
||||
* This will return a PFD to the user that represent a bound UDP socket. The system server will
|
||||
@@ -939,7 +976,8 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
}
|
||||
checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
|
||||
|
||||
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
|
||||
int callingUid = Binder.getCallingUid();
|
||||
UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
|
||||
int resourceId = mNextResourceId.getAndIncrement();
|
||||
FileDescriptor sockFd = null;
|
||||
try {
|
||||
@@ -948,6 +986,7 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
}
|
||||
|
||||
sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
mUidFdTagger.tag(sockFd, callingUid);
|
||||
|
||||
if (port != 0) {
|
||||
Log.v(TAG, "Binding to port " + port);
|
||||
|
||||
@@ -27,6 +27,7 @@ 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;
|
||||
@@ -40,10 +41,14 @@ import android.net.IpSecTransform;
|
||||
import android.net.IpSecUdpEncapResponse;
|
||||
import android.os.Binder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.StructStat;
|
||||
|
||||
import dalvik.system.SocketTagger;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.InetAddress;
|
||||
@@ -56,6 +61,7 @@ import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
|
||||
/** Unit tests for {@link IpSecService}. */
|
||||
@SmallTest
|
||||
@@ -411,4 +417,62 @@ public class IpSecServiceTest {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user