Merge "Dynamically calculate IKEv2/IPsec VPN MTU" am: 0cadebbc28 am: 7a6ea59f37
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2219661 Change-Id: I46a07ca08e85a37a820708a9e24daaf4300abf37 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -28,6 +28,7 @@ import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
|
||||
import static android.os.PowerWhitelistManager.REASON_VPN;
|
||||
import static android.os.UserHandle.PER_USER_RANGE;
|
||||
|
||||
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
|
||||
import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -84,6 +85,7 @@ import android.net.VpnManager;
|
||||
import android.net.VpnProfileState;
|
||||
import android.net.VpnService;
|
||||
import android.net.VpnTransportInfo;
|
||||
import android.net.ipsec.ike.ChildSaProposal;
|
||||
import android.net.ipsec.ike.ChildSessionCallback;
|
||||
import android.net.ipsec.ike.ChildSessionConfiguration;
|
||||
import android.net.ipsec.ike.ChildSessionParams;
|
||||
@@ -93,6 +95,7 @@ import android.net.ipsec.ike.IkeSessionConfiguration;
|
||||
import android.net.ipsec.ike.IkeSessionConnectionInfo;
|
||||
import android.net.ipsec.ike.IkeSessionParams;
|
||||
import android.net.ipsec.ike.IkeTunnelConnectionParams;
|
||||
import android.net.ipsec.ike.exceptions.IkeIOException;
|
||||
import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
|
||||
import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
|
||||
import android.net.ipsec.ike.exceptions.IkeProtocolException;
|
||||
@@ -140,6 +143,7 @@ import com.android.net.module.util.NetworkStackConstants;
|
||||
import com.android.server.DeviceIdleInternal;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.net.BaseNetworkObserver;
|
||||
import com.android.server.vcn.util.MtuUtils;
|
||||
import com.android.server.vcn.util.PersistableBundleUtils;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
@@ -152,6 +156,8 @@ import java.io.OutputStream;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -165,6 +171,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -551,6 +558,24 @@ public class Vpn {
|
||||
return DATA_STALL_RESET_DELAYS_SEC[count];
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the MTU of an interface using Java NetworkInterface primitives */
|
||||
public int getJavaNetworkInterfaceMtu(@Nullable String iface, int defaultValue)
|
||||
throws SocketException {
|
||||
if (iface == null) return defaultValue;
|
||||
|
||||
final NetworkInterface networkInterface = NetworkInterface.getByName(iface);
|
||||
return networkInterface == null ? defaultValue : networkInterface.getMTU();
|
||||
}
|
||||
|
||||
/** Calculates the VPN Network's max MTU based on underlying network and configuration */
|
||||
public int calculateVpnMtu(
|
||||
@NonNull List<ChildSaProposal> childProposals,
|
||||
int maxMtu,
|
||||
int underlyingMtu,
|
||||
boolean isIpv4) {
|
||||
return MtuUtils.getMtu(childProposals, maxMtu, underlyingMtu, isIpv4);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -1397,6 +1422,11 @@ public class Vpn {
|
||||
}
|
||||
|
||||
private LinkProperties makeLinkProperties() {
|
||||
// The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional
|
||||
// logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU
|
||||
// is lower than 1280. The logic is controlled by IKEv2VpnRunner, so the design is only
|
||||
// enabled for IKEv2 VPN.
|
||||
final boolean disableIPV6 = (isIkev2VpnRunner() && mConfig.mtu < IPV6_MIN_MTU);
|
||||
boolean allowIPv4 = mConfig.allowIPv4;
|
||||
boolean allowIPv6 = mConfig.allowIPv6;
|
||||
|
||||
@@ -1406,6 +1436,7 @@ public class Vpn {
|
||||
|
||||
if (mConfig.addresses != null) {
|
||||
for (LinkAddress address : mConfig.addresses) {
|
||||
if (disableIPV6 && address.isIpv6()) continue;
|
||||
lp.addLinkAddress(address);
|
||||
allowIPv4 |= address.getAddress() instanceof Inet4Address;
|
||||
allowIPv6 |= address.getAddress() instanceof Inet6Address;
|
||||
@@ -1414,8 +1445,9 @@ public class Vpn {
|
||||
|
||||
if (mConfig.routes != null) {
|
||||
for (RouteInfo route : mConfig.routes) {
|
||||
final InetAddress address = route.getDestination().getAddress();
|
||||
if (disableIPV6 && address instanceof Inet6Address) continue;
|
||||
lp.addRoute(route);
|
||||
InetAddress address = route.getDestination().getAddress();
|
||||
|
||||
if (route.getType() == RouteInfo.RTN_UNICAST) {
|
||||
allowIPv4 |= address instanceof Inet4Address;
|
||||
@@ -1426,7 +1458,8 @@ public class Vpn {
|
||||
|
||||
if (mConfig.dnsServers != null) {
|
||||
for (String dnsServer : mConfig.dnsServers) {
|
||||
InetAddress address = InetAddresses.parseNumericAddress(dnsServer);
|
||||
final InetAddress address = InetAddresses.parseNumericAddress(dnsServer);
|
||||
if (disableIPV6 && address instanceof Inet6Address) continue;
|
||||
lp.addDnsServer(address);
|
||||
allowIPv4 |= address instanceof Inet4Address;
|
||||
allowIPv6 |= address instanceof Inet6Address;
|
||||
@@ -1440,7 +1473,7 @@ public class Vpn {
|
||||
NetworkStackConstants.IPV4_ADDR_ANY, 0), null /*gateway*/,
|
||||
null /*iface*/, RTN_UNREACHABLE));
|
||||
}
|
||||
if (!allowIPv6) {
|
||||
if (!allowIPv6 || disableIPV6) {
|
||||
lp.addRoute(new RouteInfo(new IpPrefix(
|
||||
NetworkStackConstants.IPV6_ADDR_ANY, 0), null /*gateway*/,
|
||||
null /*iface*/, RTN_UNREACHABLE));
|
||||
@@ -1576,6 +1609,18 @@ public class Vpn {
|
||||
updateState(DetailedState.DISCONNECTED, "agentDisconnect");
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void startNewNetworkAgent(NetworkAgent oldNetworkAgent, String reason) {
|
||||
// Initialize the state for a new agent, while keeping the old one connected
|
||||
// in case this new connection fails.
|
||||
mNetworkAgent = null;
|
||||
updateState(DetailedState.CONNECTING, reason);
|
||||
// Bringing up a new NetworkAgent to prevent the data leakage before tearing down the old
|
||||
// NetworkAgent.
|
||||
agentConnect();
|
||||
agentDisconnect(oldNetworkAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a VPN network and return the file descriptor of the VPN interface. This methods
|
||||
* returns {@code null} if the application is revoked or not prepared.
|
||||
@@ -1665,16 +1710,7 @@ public class Vpn {
|
||||
setUnderlyingNetworks(config.underlyingNetworks);
|
||||
}
|
||||
} else {
|
||||
// Initialize the state for a new agent, while keeping the old one connected
|
||||
// in case this new connection fails.
|
||||
mNetworkAgent = null;
|
||||
updateState(DetailedState.CONNECTING, "establish");
|
||||
// Set up forwarding and DNS rules.
|
||||
agentConnect();
|
||||
// Remove the old tun's user forwarding rules
|
||||
// The new tun's user rules have already been added above so they will take over
|
||||
// as rules are deleted. This prevents data leakage as the rules are moved over.
|
||||
agentDisconnect(oldNetworkAgent);
|
||||
startNewNetworkAgent(oldNetworkAgent, "establish");
|
||||
}
|
||||
|
||||
if (oldConnection != null) {
|
||||
@@ -2711,6 +2747,17 @@ public class Vpn {
|
||||
void onSessionLost(int token, @Nullable Exception exception);
|
||||
}
|
||||
|
||||
private static boolean isIPv6Only(List<LinkAddress> linkAddresses) {
|
||||
boolean hasIPV6 = false;
|
||||
boolean hasIPV4 = false;
|
||||
for (final LinkAddress address : linkAddresses) {
|
||||
hasIPV6 |= address.isIpv6();
|
||||
hasIPV4 |= address.isIpv4();
|
||||
}
|
||||
|
||||
return hasIPV6 && !hasIPV4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class managing IKEv2/IPsec VPN connectivity
|
||||
*
|
||||
@@ -2924,15 +2971,27 @@ public class Vpn {
|
||||
|
||||
try {
|
||||
final String interfaceName = mTunnelIface.getInterfaceName();
|
||||
final int maxMtu = mProfile.getMaxMtu();
|
||||
final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
|
||||
final List<String> dnsAddrStrings = new ArrayList<>();
|
||||
int vpnMtu;
|
||||
vpnMtu = calculateVpnMtu();
|
||||
|
||||
// If the VPN is IPv6 only and its MTU is lower than 1280, mark the network as lost
|
||||
// and send the VpnManager event to the VPN app.
|
||||
if (isIPv6Only(internalAddresses) && vpnMtu < IPV6_MIN_MTU) {
|
||||
onSessionLost(
|
||||
token,
|
||||
new IkeIOException(
|
||||
new IOException("No valid addresses for MTU < 1280")));
|
||||
return;
|
||||
}
|
||||
|
||||
final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
|
||||
childConfig.getOutboundTrafficSelectors());
|
||||
for (final LinkAddress address : internalAddresses) {
|
||||
mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
|
||||
}
|
||||
|
||||
for (InetAddress addr : childConfig.getInternalDnsServers()) {
|
||||
dnsAddrStrings.add(addr.getHostAddress());
|
||||
}
|
||||
@@ -2950,7 +3009,7 @@ public class Vpn {
|
||||
if (mVpnRunner != this) return;
|
||||
|
||||
mInterface = interfaceName;
|
||||
mConfig.mtu = maxMtu;
|
||||
mConfig.mtu = vpnMtu;
|
||||
mConfig.interfaze = mInterface;
|
||||
|
||||
mConfig.addresses.clear();
|
||||
@@ -3053,12 +3112,54 @@ public class Vpn {
|
||||
// Ignore stale runner.
|
||||
if (mVpnRunner != this) return;
|
||||
|
||||
final LinkProperties oldLp = makeLinkProperties();
|
||||
|
||||
final boolean underlyingNetworkHasChanged =
|
||||
!Arrays.equals(mConfig.underlyingNetworks, new Network[]{network});
|
||||
mConfig.underlyingNetworks = new Network[] {network};
|
||||
mNetworkCapabilities =
|
||||
new NetworkCapabilities.Builder(mNetworkCapabilities)
|
||||
.setUnderlyingNetworks(Collections.singletonList(network))
|
||||
.build();
|
||||
doSetUnderlyingNetworks(mNetworkAgent, Collections.singletonList(network));
|
||||
mConfig.mtu = calculateVpnMtu();
|
||||
|
||||
final LinkProperties newLp = makeLinkProperties();
|
||||
|
||||
// If MTU is < 1280, IPv6 addresses will be removed. If there are no addresses
|
||||
// left (e.g. IPv6-only VPN network), mark VPN as having lost the session.
|
||||
if (newLp.getLinkAddresses().isEmpty()) {
|
||||
onSessionLost(
|
||||
token,
|
||||
new IkeIOException(
|
||||
new IOException("No valid addresses for MTU < 1280")));
|
||||
return;
|
||||
}
|
||||
|
||||
final Set<LinkAddress> removedAddrs = new HashSet<>(oldLp.getLinkAddresses());
|
||||
removedAddrs.removeAll(newLp.getLinkAddresses());
|
||||
|
||||
// If addresses were removed despite no IKE config change, IPv6 addresses must
|
||||
// have been removed due to MTU size. Restart the VPN to ensure all IPv6
|
||||
// unconnected sockets on the new VPN network are closed and retried on the new
|
||||
// VPN network.
|
||||
if (!removedAddrs.isEmpty()) {
|
||||
startNewNetworkAgent(
|
||||
mNetworkAgent, "MTU too low for IPv6; restarting network agent");
|
||||
|
||||
for (LinkAddress removed : removedAddrs) {
|
||||
mTunnelIface.removeAddress(
|
||||
removed.getAddress(), removed.getPrefixLength());
|
||||
}
|
||||
} else {
|
||||
// Put below 3 updates into else block is because agentConnect() will do
|
||||
// those things, so there is no need to do the redundant work.
|
||||
if (!newLp.equals(oldLp)) doSendLinkProperties(mNetworkAgent, newLp);
|
||||
if (underlyingNetworkHasChanged) {
|
||||
mNetworkCapabilities =
|
||||
new NetworkCapabilities.Builder(mNetworkCapabilities)
|
||||
.setUnderlyingNetworks(
|
||||
Collections.singletonList(network))
|
||||
.build();
|
||||
doSetUnderlyingNetworks(mNetworkAgent,
|
||||
Collections.singletonList(network));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mTunnelIface.setUnderlyingNetwork(network);
|
||||
@@ -3108,6 +3209,60 @@ public class Vpn {
|
||||
startOrMigrateIkeSession(network);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private IkeSessionParams getIkeSessionParams(@NonNull Network underlyingNetwork) {
|
||||
final IkeTunnelConnectionParams ikeTunConnParams =
|
||||
mProfile.getIkeTunnelConnectionParams();
|
||||
if (ikeTunConnParams != null) {
|
||||
final IkeSessionParams.Builder builder =
|
||||
new IkeSessionParams.Builder(ikeTunConnParams.getIkeSessionParams())
|
||||
.setNetwork(underlyingNetwork);
|
||||
return builder.build();
|
||||
} else {
|
||||
return VpnIkev2Utils.buildIkeSessionParams(mContext, mProfile, underlyingNetwork);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ChildSessionParams getChildSessionParams() {
|
||||
final IkeTunnelConnectionParams ikeTunConnParams =
|
||||
mProfile.getIkeTunnelConnectionParams();
|
||||
if (ikeTunConnParams != null) {
|
||||
return ikeTunConnParams.getTunnelModeChildSessionParams();
|
||||
} else {
|
||||
return VpnIkev2Utils.buildChildSessionParams(mProfile.getAllowedAlgorithms());
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateVpnMtu() {
|
||||
final Network underlyingNetwork = mIkeConnectionInfo.getNetwork();
|
||||
final LinkProperties lp = mConnectivityManager.getLinkProperties(underlyingNetwork);
|
||||
if (underlyingNetwork == null || lp == null) {
|
||||
// Return the max MTU defined in VpnProfile as the fallback option when there is no
|
||||
// underlying network or LinkProperties is null.
|
||||
return mProfile.getMaxMtu();
|
||||
}
|
||||
|
||||
int underlyingMtu = lp.getMtu();
|
||||
|
||||
// Try to get MTU from kernel if MTU is not set in LinkProperties.
|
||||
if (underlyingMtu == 0) {
|
||||
try {
|
||||
underlyingMtu = mDeps.getJavaNetworkInterfaceMtu(lp.getInterfaceName(),
|
||||
mProfile.getMaxMtu());
|
||||
} catch (SocketException e) {
|
||||
Log.d(TAG, "Got a SocketException when getting MTU from kernel: " + e);
|
||||
return mProfile.getMaxMtu();
|
||||
}
|
||||
}
|
||||
|
||||
return mDeps.calculateVpnMtu(
|
||||
getChildSessionParams().getSaProposals(),
|
||||
mProfile.getMaxMtu(),
|
||||
underlyingMtu,
|
||||
mIkeConnectionInfo.getLocalAddress() instanceof Inet4Address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new IKE session.
|
||||
*
|
||||
@@ -3158,24 +3313,6 @@ public class Vpn {
|
||||
// (non-default) network, and start the new one.
|
||||
resetIkeState();
|
||||
|
||||
// Get Ike options from IkeTunnelConnectionParams if it's available in the
|
||||
// profile.
|
||||
final IkeTunnelConnectionParams ikeTunConnParams =
|
||||
mProfile.getIkeTunnelConnectionParams();
|
||||
final IkeSessionParams ikeSessionParams;
|
||||
final ChildSessionParams childSessionParams;
|
||||
if (ikeTunConnParams != null) {
|
||||
final IkeSessionParams.Builder builder = new IkeSessionParams.Builder(
|
||||
ikeTunConnParams.getIkeSessionParams()).setNetwork(underlyingNetwork);
|
||||
ikeSessionParams = builder.build();
|
||||
childSessionParams = ikeTunConnParams.getTunnelModeChildSessionParams();
|
||||
} else {
|
||||
ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams(
|
||||
mContext, mProfile, underlyingNetwork);
|
||||
childSessionParams = VpnIkev2Utils.buildChildSessionParams(
|
||||
mProfile.getAllowedAlgorithms());
|
||||
}
|
||||
|
||||
// TODO: Remove the need for adding two unused addresses with
|
||||
// IPsec tunnels.
|
||||
final InetAddress address = InetAddress.getLocalHost();
|
||||
@@ -3193,8 +3330,8 @@ public class Vpn {
|
||||
mSession =
|
||||
mIkev2SessionCreator.createIkeSession(
|
||||
mContext,
|
||||
ikeSessionParams,
|
||||
childSessionParams,
|
||||
getIkeSessionParams(underlyingNetwork),
|
||||
getChildSessionParams(),
|
||||
mExecutor,
|
||||
new VpnIkev2Utils.IkeSessionCallbackImpl(
|
||||
TAG, IkeV2VpnRunner.this, token),
|
||||
|
||||
Reference in New Issue
Block a user