Obtain the MTU from a socket to the destination

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: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> 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
diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
index a367d9d..e1e2585 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.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.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 @@
             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 @@
         }
 
         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 @@
             // 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 @@
         }
     }
 
+    /**
+     * 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();