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;
|
package com.android.server;
|
||||||
|
|
||||||
import static android.Manifest.permission.DUMP;
|
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.FEATURE_IPSEC_TUNNEL_MIGRATION;
|
||||||
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
|
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
|
||||||
import static android.system.OsConstants.AF_INET;
|
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.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.Preconditions;
|
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.BinderUtils;
|
||||||
import com.android.net.module.util.NetdUtils;
|
import com.android.net.module.util.NetdUtils;
|
||||||
import com.android.net.module.util.PermissionUtils;
|
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 int NETD_FETCH_TIMEOUT_MS = 5000; // ms
|
||||||
private static final InetAddress INADDR_ANY;
|
private static final InetAddress INADDR_ANY;
|
||||||
|
private static final InetAddress IN6ADDR_ANY;
|
||||||
|
|
||||||
@VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
|
@VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
|
||||||
|
|
||||||
@@ -110,6 +113,8 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
|
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) {
|
} catch (UnknownHostException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -1013,11 +1018,13 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
private final class EncapSocketRecord extends OwnedResourceRecord {
|
private final class EncapSocketRecord extends OwnedResourceRecord {
|
||||||
private FileDescriptor mSocket;
|
private FileDescriptor mSocket;
|
||||||
private final int mPort;
|
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);
|
super(resourceId);
|
||||||
mSocket = socket;
|
mSocket = socket;
|
||||||
mPort = port;
|
mPort = port;
|
||||||
|
mFamily = family;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** always guarded by IpSecService#this */
|
/** always guarded by IpSecService#this */
|
||||||
@@ -1038,6 +1045,10 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
return mSocket;
|
return mSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getFamily() {
|
||||||
|
return mFamily;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ResourceTracker getResourceTracker() {
|
protected ResourceTracker getResourceTracker() {
|
||||||
return getUserRecord().mSocketQuotaTracker;
|
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
|
* and re-binding, during which the system could *technically* hand that port out to someone
|
||||||
* else.
|
* 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--) {
|
for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
|
||||||
try {
|
try {
|
||||||
FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
FileDescriptor probeSocket = Os.socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
Os.bind(probeSocket, INADDR_ANY, 0);
|
Os.bind(probeSocket, any, 0);
|
||||||
int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
|
int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
|
||||||
Os.close(probeSocket);
|
Os.close(probeSocket);
|
||||||
Log.v(TAG, "Binding to port " + port);
|
Log.v(TAG, "Binding to port " + port);
|
||||||
Os.bind(sockFd, INADDR_ANY, port);
|
Os.bind(sockFd, any, port);
|
||||||
return port;
|
return port;
|
||||||
} catch (ErrnoException e) {
|
} catch (ErrnoException e) {
|
||||||
// Someone miraculously claimed the port just after we closed probeSocket.
|
// Someone miraculously claimed the port just after we closed probeSocket.
|
||||||
@@ -1260,6 +1272,19 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
|
public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
|
||||||
throws RemoteException {
|
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)) {
|
if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Specified port number must be a valid non-reserved UDP port");
|
"Specified port number must be a valid non-reserved UDP port");
|
||||||
@@ -1278,7 +1303,7 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
|
|
||||||
FileDescriptor sockFd = null;
|
FileDescriptor sockFd = null;
|
||||||
try {
|
try {
|
||||||
sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
sockFd = Os.socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
pFd = ParcelFileDescriptor.dup(sockFd);
|
pFd = ParcelFileDescriptor.dup(sockFd);
|
||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(sockFd);
|
IoUtils.closeQuietly(sockFd);
|
||||||
@@ -1295,15 +1320,16 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
mNetd.ipSecSetEncapSocketOwner(pFd, callingUid);
|
mNetd.ipSecSetEncapSocketOwner(pFd, callingUid);
|
||||||
if (port != 0) {
|
if (port != 0) {
|
||||||
Log.v(TAG, "Binding to port " + port);
|
Log.v(TAG, "Binding to port " + port);
|
||||||
Os.bind(pFd.getFileDescriptor(), INADDR_ANY, port);
|
Os.bind(pFd.getFileDescriptor(), localAddr, port);
|
||||||
} else {
|
} else {
|
||||||
port = bindToRandomPort(pFd.getFileDescriptor());
|
port = bindToRandomPort(pFd.getFileDescriptor(), family);
|
||||||
}
|
}
|
||||||
|
|
||||||
userRecord.mEncapSocketRecords.put(
|
userRecord.mEncapSocketRecords.put(
|
||||||
resourceId,
|
resourceId,
|
||||||
new RefcountedResource<EncapSocketRecord>(
|
new RefcountedResource<EncapSocketRecord>(
|
||||||
new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port),
|
new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port,
|
||||||
|
family),
|
||||||
binder));
|
binder));
|
||||||
return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port,
|
return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port,
|
||||||
pFd.getFileDescriptor());
|
pFd.getFileDescriptor());
|
||||||
@@ -1580,6 +1606,7 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
*/
|
*/
|
||||||
private void checkIpSecConfig(IpSecConfig config) {
|
private void checkIpSecConfig(IpSecConfig config) {
|
||||||
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
|
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
|
||||||
|
EncapSocketRecord encapSocketRecord = null;
|
||||||
|
|
||||||
switch (config.getEncapType()) {
|
switch (config.getEncapType()) {
|
||||||
case IpSecTransform.ENCAP_NONE:
|
case IpSecTransform.ENCAP_NONE:
|
||||||
@@ -1587,7 +1614,7 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
case IpSecTransform.ENCAP_ESPINUDP:
|
case IpSecTransform.ENCAP_ESPINUDP:
|
||||||
case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
|
case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
|
||||||
// Retrieve encap socket record; will throw IllegalArgumentException if not found
|
// Retrieve encap socket record; will throw IllegalArgumentException if not found
|
||||||
userRecord.mEncapSocketRecords.getResourceOrThrow(
|
encapSocketRecord = userRecord.mEncapSocketRecords.getResourceOrThrow(
|
||||||
config.getEncapSocketResourceId());
|
config.getEncapSocketResourceId());
|
||||||
|
|
||||||
int port = config.getEncapRemotePort();
|
int port = config.getEncapRemotePort();
|
||||||
@@ -1641,10 +1668,9 @@ public class IpSecService extends IIpSecService.Stub {
|
|||||||
+ ") have different address families.");
|
+ ") have different address families.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw an error if UDP Encapsulation is not used in IPv4.
|
if (encapSocketRecord != null && encapSocketRecord.getFamily() != destinationFamily) {
|
||||||
if (config.getEncapType() != IpSecTransform.ENCAP_NONE && sourceFamily != AF_INET) {
|
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"UDP Encapsulation is not supported for this address family");
|
"UDP encapsulation socket and destination address families must match");
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (config.getMode()) {
|
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.getFirstApiLevel;
|
||||||
import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
|
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.MiscAsserts.assertThrows;
|
||||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ import android.system.OsConstants;
|
|||||||
import androidx.test.InstrumentationRegistry;
|
import androidx.test.InstrumentationRegistry;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.modules.utils.build.SdkLevel;
|
||||||
import com.android.testutils.DevSdkIgnoreRule;
|
import com.android.testutils.DevSdkIgnoreRule;
|
||||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
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
|
@Test
|
||||||
public void testCreateTransformIpv4() throws Exception {
|
public void testCreateTransformIpv4() throws Exception {
|
||||||
doTestCreateTransform(IPV4_LOOPBACK, false);
|
doTestCreateTransform(IPV4_LOOPBACK, false);
|
||||||
@@ -377,6 +385,12 @@ public class IpSecManagerTest extends IpSecBaseTest {
|
|||||||
doTestCreateTransform(IPV4_LOOPBACK, true);
|
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 */
|
/** Snapshot of TrafficStats as of initStatsChecker call for later comparisons */
|
||||||
private static class StatsChecker {
|
private static class StatsChecker {
|
||||||
private static final double ERROR_MARGIN_BYTES = 1.05;
|
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_ENCAP_SOCKETS = 100;
|
||||||
private static final int MAX_NUM_SPIS = 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_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;
|
private static final InetAddress INADDR_ANY;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user