From 65996edf36e962a381f9c913551d6b87ac225388 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Thu, 22 Jun 2023 15:51:32 +0900 Subject: [PATCH] Obtain the MTU from a socket to the destination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MTU from the socket is a much easier way to get the correct MTU to any given destination. In particular this is important for CLAT which used to be wrong : on my device, with a perfectly working GoogleGuest : 50: v4-wlan0: mtu 1472 qdisc pfifo_fast state UNKNOWN group default qlen 500 F ICMPv4 payloadLength{1472} dst{8.8.8.8} src{192.0.0.4:27}: FAILED: 0/15 (4811ms) After this patch, the payload is correctly 1444. Test: Manual on GoogleGuest Manual on a network with a custom set MTU Change-Id: I6cd2398572b3fe08f88832ab6d0aa715c118cd4f --- .../connectivity/NetworkDiagnostics.java | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java index a367d9d68f..e1e2585aea 100644 --- a/service/src/com/android/server/connectivity/NetworkDiagnostics.java +++ b/service/src/com/android/server/connectivity/NetworkDiagnostics.java @@ -24,9 +24,12 @@ import static com.android.net.module.util.NetworkStackConstants.ICMP_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; +import static com.android.net.module.util.NetworkStackConstants.IPV6_MTU; +import static com.android.net.module.util.NetworkStackConstants.IP_MTU; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TargetApi; import android.net.InetAddresses; import android.net.LinkAddress; import android.net.LinkProperties; @@ -35,6 +38,7 @@ import android.net.RouteInfo; import android.net.TrafficStats; import android.net.shared.PrivateDnsConfig; import android.net.util.NetworkConstants; +import android.os.Build; import android.os.SystemClock; import android.system.ErrnoException; import android.system.Os; @@ -213,14 +217,10 @@ public class NetworkDiagnostics { mLinkProperties.addDnsServer(TEST_DNS6); } - final int lpMtu = mLinkProperties.getMtu(); - final int mtu = lpMtu > 0 ? lpMtu : ETHER_MTU; for (RouteInfo route : mLinkProperties.getRoutes()) { if (route.getType() == RouteInfo.RTN_UNICAST && route.hasGateway()) { - InetAddress gateway = route.getGateway(); - // Use mtu in the route if exists. Otherwise, use the one in the link property. - final int routeMtu = route.getMtu(); - prepareIcmpMeasurements(gateway, (routeMtu > 0) ? routeMtu : mtu); + final InetAddress gateway = route.getGateway(); + prepareIcmpMeasurements(gateway); if (route.isIPv6Default()) { prepareExplicitSourceIcmpMeasurements(gateway); } @@ -228,7 +228,7 @@ public class NetworkDiagnostics { } for (InetAddress nameserver : mLinkProperties.getDnsServers()) { - prepareIcmpMeasurements(nameserver, mtu); + prepareIcmpMeasurements(nameserver); prepareDnsMeasurement(nameserver); // Unlike the DnsResolver which doesn't do certificate validation in opportunistic mode, @@ -285,24 +285,29 @@ public class NetworkDiagnostics { // calculation. if (addr instanceof Inet6Address) { return IPV6_HEADER_LEN + ICMP_HEADER_LEN; + } else { + return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN; } } catch (UnknownHostException e) { - Log.e(TAG, "Create InetAddress fail(" + target + "): " + e); + throw new AssertionError("Create InetAddress fail(" + target + ")", e); } - - return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN; } - private void prepareIcmpMeasurements(@NonNull InetAddress target, int targetNetworkMtu) { + private void prepareIcmpMeasurements(@NonNull InetAddress target) { + int mtu = getMtuForTarget(target); + // If getMtuForTarget fails, it doesn't matter what mtu is used because connect can't + // succeed anyway + if (mtu <= 0) mtu = mLinkProperties.getMtu(); + if (mtu <= 0) mtu = ETHER_MTU; // Test with different size payload ICMP. // 1. Test with 0 payload. addPayloadIcmpMeasurement(target, 0); final int header = getHeaderLen(target); // 2. Test with full size MTU. - addPayloadIcmpMeasurement(target, targetNetworkMtu - header); + addPayloadIcmpMeasurement(target, mtu - header); // 3. If v6, make another measurement with the full v6 min MTU, unless that's what // was done above. - if ((target instanceof Inet6Address) && (targetNetworkMtu != IPV6_MIN_MTU)) { + if ((target instanceof Inet6Address) && (mtu != IPV6_MIN_MTU)) { addPayloadIcmpMeasurement(target, IPV6_MIN_MTU - header); } } @@ -321,6 +326,35 @@ public class NetworkDiagnostics { } } + /** + * Open a socket to the target address and return the mtu from that socket + * + * If the MTU can't be obtained for some reason (e.g. the target is unreachable) this will + * return -1. + * + * @param target the destination address + * @return the mtu to that destination, or -1 + */ + // getsockoptInt is S+, but this service code and only installs on S, so it's safe to ignore + // the lint warnings by using @TargetApi. + @TargetApi(Build.VERSION_CODES.S) + private int getMtuForTarget(InetAddress target) { + final int family = target instanceof Inet4Address ? AF_INET : AF_INET6; + try { + final FileDescriptor socket = Os.socket(family, SOCK_DGRAM, 0); + mNetwork.bindSocket(socket); + Os.connect(socket, target, 0); + if (family == AF_INET) { + return Os.getsockoptInt(socket, IPPROTO_IP, IP_MTU); + } else { + return Os.getsockoptInt(socket, IPPROTO_IPV6, IPV6_MTU); + } + } catch (ErrnoException | IOException e) { + Log.e(TAG, "Can't get MTU for destination " + target, e); + return -1; + } + } + private void prepareExplicitSourceIcmpMeasurements(InetAddress target) { for (LinkAddress l : mLinkProperties.getLinkAddresses()) { InetAddress source = l.getAddress();