Experimental support for IPv6 UDP encap.
This is a back-pocket solution only, to ensure that VpnManager privileged clients can temporarily rely on IPv6 UDP encap if on certain carriers IPv4 UDP and IPv6 ESP cannot provide acceptable performance and battery life. For these reasons IPv6 UDP encap is not a public or system API and is triggered by passing a port greater than 65535 to the existing openUdpEncapsulationSocket API. Bug: 259001350 Test: new CTS tests Change-Id: I02e0566ba910a300dda6a589cd265a3360add40c
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package com.android.server;
|
||||
|
||||
import static android.Manifest.permission.DUMP;
|
||||
import static android.Manifest.permission.NETWORK_SETTINGS;
|
||||
import static android.net.IpSecManager.FEATURE_IPSEC_TUNNEL_MIGRATION;
|
||||
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
|
||||
import static android.system.OsConstants.AF_INET;
|
||||
@@ -65,6 +66,7 @@ import android.util.SparseBooleanArray;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.net.module.util.BinderUtils;
|
||||
import com.android.net.module.util.NetdUtils;
|
||||
import com.android.net.module.util.PermissionUtils;
|
||||
@@ -102,6 +104,7 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
|
||||
private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
|
||||
private static final InetAddress INADDR_ANY;
|
||||
private static final InetAddress IN6ADDR_ANY;
|
||||
|
||||
@VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
|
||||
|
||||
@@ -110,6 +113,8 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
static {
|
||||
try {
|
||||
INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
|
||||
IN6ADDR_ANY = InetAddress.getByAddress(
|
||||
new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -1013,11 +1018,13 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
private final class EncapSocketRecord extends OwnedResourceRecord {
|
||||
private FileDescriptor mSocket;
|
||||
private final int mPort;
|
||||
private final int mFamily; // TODO: what about IPV6_ADDRFORM?
|
||||
|
||||
EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
|
||||
EncapSocketRecord(int resourceId, FileDescriptor socket, int port, int family) {
|
||||
super(resourceId);
|
||||
mSocket = socket;
|
||||
mPort = port;
|
||||
mFamily = family;
|
||||
}
|
||||
|
||||
/** always guarded by IpSecService#this */
|
||||
@@ -1038,6 +1045,10 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
return mSocket;
|
||||
}
|
||||
|
||||
public int getFamily() {
|
||||
return mFamily;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceTracker getResourceTracker() {
|
||||
return getUserRecord().mSocketQuotaTracker;
|
||||
@@ -1210,15 +1221,16 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
* and re-binding, during which the system could *technically* hand that port out to someone
|
||||
* else.
|
||||
*/
|
||||
private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
|
||||
private int bindToRandomPort(FileDescriptor sockFd, int family) throws IOException {
|
||||
final InetAddress any = (family == AF_INET6) ? IN6ADDR_ANY : INADDR_ANY;
|
||||
for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
|
||||
try {
|
||||
FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
Os.bind(probeSocket, INADDR_ANY, 0);
|
||||
FileDescriptor probeSocket = Os.socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
Os.bind(probeSocket, any, 0);
|
||||
int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
|
||||
Os.close(probeSocket);
|
||||
Log.v(TAG, "Binding to port " + port);
|
||||
Os.bind(sockFd, INADDR_ANY, port);
|
||||
Os.bind(sockFd, any, port);
|
||||
return port;
|
||||
} catch (ErrnoException e) {
|
||||
// Someone miraculously claimed the port just after we closed probeSocket.
|
||||
@@ -1260,6 +1272,19 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
@Override
|
||||
public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
|
||||
throws RemoteException {
|
||||
// Experimental support for IPv6 UDP encap.
|
||||
final int family;
|
||||
final InetAddress localAddr;
|
||||
if (SdkLevel.isAtLeastU() && port >= 65536) {
|
||||
PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
|
||||
port -= 65536;
|
||||
family = AF_INET6;
|
||||
localAddr = IN6ADDR_ANY;
|
||||
} else {
|
||||
family = AF_INET;
|
||||
localAddr = INADDR_ANY;
|
||||
}
|
||||
|
||||
if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified port number must be a valid non-reserved UDP port");
|
||||
@@ -1278,7 +1303,7 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
|
||||
FileDescriptor sockFd = null;
|
||||
try {
|
||||
sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
sockFd = Os.socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
pFd = ParcelFileDescriptor.dup(sockFd);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(sockFd);
|
||||
@@ -1295,15 +1320,16 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
mNetd.ipSecSetEncapSocketOwner(pFd, callingUid);
|
||||
if (port != 0) {
|
||||
Log.v(TAG, "Binding to port " + port);
|
||||
Os.bind(pFd.getFileDescriptor(), INADDR_ANY, port);
|
||||
Os.bind(pFd.getFileDescriptor(), localAddr, port);
|
||||
} else {
|
||||
port = bindToRandomPort(pFd.getFileDescriptor());
|
||||
port = bindToRandomPort(pFd.getFileDescriptor(), family);
|
||||
}
|
||||
|
||||
userRecord.mEncapSocketRecords.put(
|
||||
resourceId,
|
||||
new RefcountedResource<EncapSocketRecord>(
|
||||
new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port),
|
||||
new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port,
|
||||
family),
|
||||
binder));
|
||||
return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port,
|
||||
pFd.getFileDescriptor());
|
||||
@@ -1580,6 +1606,7 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
*/
|
||||
private void checkIpSecConfig(IpSecConfig config) {
|
||||
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
|
||||
EncapSocketRecord encapSocketRecord = null;
|
||||
|
||||
switch (config.getEncapType()) {
|
||||
case IpSecTransform.ENCAP_NONE:
|
||||
@@ -1587,7 +1614,7 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
case IpSecTransform.ENCAP_ESPINUDP:
|
||||
case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
|
||||
// Retrieve encap socket record; will throw IllegalArgumentException if not found
|
||||
userRecord.mEncapSocketRecords.getResourceOrThrow(
|
||||
encapSocketRecord = userRecord.mEncapSocketRecords.getResourceOrThrow(
|
||||
config.getEncapSocketResourceId());
|
||||
|
||||
int port = config.getEncapRemotePort();
|
||||
@@ -1641,10 +1668,9 @@ public class IpSecService extends IIpSecService.Stub {
|
||||
+ ") have different address families.");
|
||||
}
|
||||
|
||||
// Throw an error if UDP Encapsulation is not used in IPv4.
|
||||
if (config.getEncapType() != IpSecTransform.ENCAP_NONE && sourceFamily != AF_INET) {
|
||||
if (encapSocketRecord != null && encapSocketRecord.getFamily() != destinationFamily) {
|
||||
throw new IllegalArgumentException(
|
||||
"UDP Encapsulation is not supported for this address family");
|
||||
"UDP encapsulation socket and destination address families must match");
|
||||
}
|
||||
|
||||
switch (config.getMode()) {
|
||||
|
||||
@@ -53,6 +53,7 @@ import static android.system.OsConstants.IPPROTO_UDP;
|
||||
|
||||
import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
|
||||
import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
|
||||
import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
|
||||
import static com.android.testutils.MiscAsserts.assertThrows;
|
||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||
|
||||
@@ -77,6 +78,7 @@ import android.system.OsConstants;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
||||
|
||||
@@ -362,6 +364,12 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
||||
});
|
||||
}
|
||||
|
||||
private void assumeExperimentalIpv6UdpEncapSupported() throws Exception {
|
||||
assumeTrue("Not supported before U", SdkLevel.isAtLeastU());
|
||||
assumeTrue("Not supported by kernel", isKernelVersionAtLeast("5.15.31")
|
||||
|| (isKernelVersionAtLeast("5.10.108") && !isKernelVersionAtLeast("5.15.0")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTransformIpv4() throws Exception {
|
||||
doTestCreateTransform(IPV4_LOOPBACK, false);
|
||||
@@ -377,6 +385,12 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
||||
doTestCreateTransform(IPV4_LOOPBACK, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTransformIpv6Encap() throws Exception {
|
||||
assumeExperimentalIpv6UdpEncapSupported();
|
||||
doTestCreateTransform(IPV6_LOOPBACK, true);
|
||||
}
|
||||
|
||||
/** Snapshot of TrafficStats as of initStatsChecker call for later comparisons */
|
||||
private static class StatsChecker {
|
||||
private static final double ERROR_MARGIN_BYTES = 1.05;
|
||||
|
||||
@@ -82,7 +82,7 @@ public class IpSecServiceTest {
|
||||
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 int TEST_UDP_ENCAP_PORT_OUT_RANGE = 200000;
|
||||
|
||||
private static final InetAddress INADDR_ANY;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user