Merge "[MS11] Implement findL2Key"
am: 773f3f5bf3

Change-Id: If429108eddc802f0c13f7b10048b1fe87bd51b22
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index 5397b57..6a9eae0 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -252,6 +252,12 @@
         }
     }
 
+    /** @hide */
+    public boolean isEmpty() {
+        return (null == assignedV4Address) && (null == groupHint)
+                && (null == dnsAddresses) && (null == mtu);
+    }
+
     @Override
     public boolean equals(@Nullable final Object o) {
         if (!(o instanceof NetworkAttributes)) return false;
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index 32513c1..e99dd4f 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -21,9 +21,12 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQuery;
 import android.net.NetworkUtils;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.Status;
@@ -35,6 +38,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.StringJoiner;
 
 /**
  * Encapsulating class for using the SQLite database backing the memory store.
@@ -46,6 +50,9 @@
  */
 public class IpMemoryStoreDatabase {
     private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
+    // A pair of NetworkAttributes objects is group-close if the confidence that they are
+    // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse.
+    private static final float GROUPCLOSE_CONFIDENCE = 0.5f;
 
     /**
      * Contract class for the Network Attributes table.
@@ -187,30 +194,35 @@
         return addresses;
     }
 
+    @NonNull
+    private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) {
+        final ContentValues values = new ContentValues();
+        if (null == attributes) return values;
+        if (null != attributes.assignedV4Address) {
+            values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
+                    NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+        }
+        if (null != attributes.groupHint) {
+            values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+        }
+        if (null != attributes.dnsAddresses) {
+            values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
+                    encodeAddressList(attributes.dnsAddresses));
+        }
+        if (null != attributes.mtu) {
+            values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
+        }
+        return values;
+    }
+
     // Convert a NetworkAttributes object to content values to store them in a table compliant
     // with the contract defined in NetworkAttributesContract.
     @NonNull
     private static ContentValues toContentValues(@NonNull final String key,
             @Nullable final NetworkAttributes attributes, final long expiry) {
-        final ContentValues values = new ContentValues();
+        final ContentValues values = toContentValues(attributes);
         values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
         values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
-        if (null != attributes) {
-            if (null != attributes.assignedV4Address) {
-                values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
-                        NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
-            }
-            if (null != attributes.groupHint) {
-                values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
-            }
-            if (null != attributes.dnsAddresses) {
-                values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
-                        encodeAddressList(attributes.dnsAddresses));
-            }
-            if (null != attributes.mtu) {
-                values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
-            }
-        }
         return values;
     }
 
@@ -228,6 +240,32 @@
         return values;
     }
 
+    @Nullable
+    private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) {
+        // Make sure the data hasn't expired
+        final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L);
+        if (expiry < System.currentTimeMillis()) return null;
+
+        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+        final int assignedV4AddressInt = getInt(cursor,
+                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+        final byte[] dnsAddressesBlob =
+                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
+        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+        if (0 != assignedV4AddressInt) {
+            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+        }
+        builder.setGroupHint(groupHint);
+        if (null != dnsAddressesBlob) {
+            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
+        }
+        if (mtu >= 0) {
+            builder.setMtu(mtu);
+        }
+        return builder.build();
+    }
+
     private static final String[] EXPIRY_COLUMN = new String[] {
         NetworkAttributesContract.COLNAME_EXPIRYDATE
     };
@@ -313,32 +351,9 @@
         // result here. 0 results means the key was not found.
         if (cursor.getCount() != 1) return null;
         cursor.moveToFirst();
-
-        // Make sure the data hasn't expired
-        final long expiry = cursor.getLong(
-                cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE));
-        if (expiry < System.currentTimeMillis()) return null;
-
-        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
-        final int assignedV4AddressInt = getInt(cursor,
-                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
-        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
-        final byte[] dnsAddressesBlob =
-                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
-        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+        final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
         cursor.close();
-
-        if (0 != assignedV4AddressInt) {
-            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
-        }
-        builder.setGroupHint(groupHint);
-        if (null != dnsAddressesBlob) {
-            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
-        }
-        if (mtu >= 0) {
-            builder.setMtu(mtu);
-        }
-        return builder.build();
+        return attributes;
     }
 
     private static final String[] DATA_COLUMN = new String[] {
@@ -365,17 +380,134 @@
         return result;
     }
 
+    /**
+     * The following is a horrible hack that is necessary because the Android SQLite API does not
+     * have a way to query a binary blob. This, almost certainly, is an overlook.
+     *
+     * The Android SQLite API has two family of methods : one for query that returns data, and
+     * one for more general SQL statements that can execute any statement but may not return
+     * anything. All the query methods, however, take only String[] for the arguments.
+     *
+     * In principle it is simple to write a function that will encode the binary blob in the
+     * way SQLite expects it. However, because the API forces the argument to be coerced into a
+     * String, the SQLiteQuery object generated by the default query methods will bind all
+     * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types,
+     * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will
+     * be sanitized, changing the contents of the field, and the query will fail to match the
+     * blob.
+     *
+     * As far as I can tell, there are two possible ways around this problem. The first one
+     * is to put the data in the query string and eschew it being an argument. This would
+     * require doing the sanitizing by hand. The other is to call bindBlob directly on the
+     * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out
+     * sanitizing, but also will do the right thing if the underlying format ever changes.
+     *
+     * But none of the methods that take an SQLiteQuery object can return data ; this *must*
+     * be called with SQLiteDatabase#query. This object is not accessible from outside.
+     * However, there is a #query version that accepts a CursorFactory and this is pretty
+     * straightforward to implement as all the arguments are coming in and the SQLiteCursor
+     * class is public API.
+     * With this, it's possible to intercept the SQLiteQuery object, and assuming the args
+     * are available, to bind them directly and work around the API's oblivious coercion into
+     * Strings.
+     *
+     * This is really sad, but I don't see another way of having this work than this or the
+     * hand-rolled sanitizing, and this is the lesser evil.
+     */
+    private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory {
+        @NonNull
+        private final ArrayList<Object> mArgs;
+        CustomCursorFactory(@NonNull final ArrayList<Object> args) {
+            mArgs = args;
+        }
+        @Override
+        public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery,
+                final String editTable,
+                final SQLiteQuery query) {
+            int index = 1; // bind is 1-indexed
+            for (final Object arg : mArgs) {
+                if (arg instanceof String) {
+                    query.bindString(index++, (String) arg);
+                } else if (arg instanceof Long) {
+                    query.bindLong(index++, (Long) arg);
+                } else if (arg instanceof Integer) {
+                    query.bindLong(index++, Long.valueOf((Integer) arg));
+                } else if (arg instanceof byte[]) {
+                    query.bindBlob(index++, (byte[]) arg);
+                } else {
+                    throw new IllegalStateException("Unsupported type CustomCursorFactory "
+                            + arg.getClass().toString());
+                }
+            }
+            return new SQLiteCursor(masterQuery, editTable, query);
+        }
+    }
+
+    // Returns the l2key of the closest match, if and only if it matches
+    // closely enough (as determined by group-closeness).
+    @Nullable
+    static String findClosestAttributes(@NonNull final SQLiteDatabase db,
+            @NonNull final NetworkAttributes attr) {
+        if (attr.isEmpty()) return null;
+        final ContentValues values = toContentValues(attr);
+
+        // Build the selection and args. To cut down on the number of lines to search, limit
+        // the search to those with at least one argument equals to the requested attributes.
+        // This works only because null attributes match only will not result in group-closeness.
+        final StringJoiner sj = new StringJoiner(" OR ");
+        final ArrayList<Object> args = new ArrayList<>();
+        args.add(System.currentTimeMillis());
+        for (final String field : values.keySet()) {
+            sj.add(field + " = ?");
+            args.add(values.get(field));
+        }
+
+        final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
+                + sj.toString() + ")";
+        final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
+                false, // distinct
+                NetworkAttributesContract.TABLENAME,
+                null, // columns, null means everything
+                selection, // selection
+                null, // selectionArgs, horrendously passed to the cursor factory instead
+                null, // groupBy
+                null, // having
+                null, // orderBy
+                null); // limit
+        if (cursor.getCount() <= 0) return null;
+        cursor.moveToFirst();
+        String bestKey = null;
+        float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
+        while (!cursor.isAfterLast()) {
+            final NetworkAttributes read = readNetworkAttributesLine(cursor);
+            final float confidence = read.getNetworkGroupSamenessConfidence(attr);
+            if (confidence > bestMatchConfidence) {
+                bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
+                bestMatchConfidence = confidence;
+            }
+            cursor.moveToNext();
+        }
+        cursor.close();
+        return bestKey;
+    }
+
     // Helper methods
-    static String getString(final Cursor cursor, final String columnName) {
+    private static String getString(final Cursor cursor, final String columnName) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
     }
-    static byte[] getBlob(final Cursor cursor, final String columnName) {
+    private static byte[] getBlob(final Cursor cursor, final String columnName) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
     }
-    static int getInt(final Cursor cursor, final String columnName, final int defaultValue) {
+    private static int getInt(final Cursor cursor, final String columnName,
+            final int defaultValue) {
         final int columnIndex = cursor.getColumnIndex(columnName);
         return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
     }
+    private static long getLong(final Cursor cursor, final String columnName,
+            final long defaultValue) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
+    }
 }
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 8b521f4..d43dc6a 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -250,9 +250,26 @@
      * Through the listener, returns the L2 key if one matched, or null.
      */
     @Override
-    public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
-            @NonNull final IOnL2KeyResponseListener listener) {
-        // TODO : implement this.
+    public void findL2Key(@Nullable final NetworkAttributesParcelable attributes,
+            @Nullable final IOnL2KeyResponseListener listener) {
+        if (null == listener) return;
+        mExecutor.execute(() -> {
+            try {
+                if (null == attributes) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    return;
+                }
+                final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb,
+                        new NetworkAttributes(attributes));
+                listener.onL2KeyResponse(makeStatus(SUCCESS), key);
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
     }
 
     /**
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index c748d0f..f2ecef9 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
 import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
 import android.net.ipmemorystore.IOnSameNetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
@@ -67,7 +68,14 @@
     private static final String TEST_CLIENT_ID = "testClientId";
     private static final String TEST_DATA_NAME = "testData";
 
-    private static final String[] FAKE_KEYS = { "fakeKey1", "fakeKey2", "fakeKey3", "fakeKey4" };
+    private static final int FAKE_KEY_COUNT = 20;
+    private static final String[] FAKE_KEYS;
+    static {
+        FAKE_KEYS = new String[FAKE_KEY_COUNT];
+        for (int i = 0; i < FAKE_KEYS.length; ++i) {
+            FAKE_KEYS[i] = "fakeKey" + i;
+        }
+    }
 
     @Mock
     private Context mMockContext;
@@ -170,6 +178,25 @@
         };
     }
 
+    /** Helper method to make an IOnL2KeyResponseListener */
+    private interface OnL2KeyResponseListener {
+        void onL2KeyResponse(Status status, String key);
+    }
+    private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) {
+        return new IOnL2KeyResponseListener() {
+            @Override
+            public void onL2KeyResponse(final StatusParcelable status, final String key)
+                    throws RemoteException {
+                functor.onL2KeyResponse(new Status(status), key);
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+    }
+
     // Helper method to factorize some boilerplate
     private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
         final CountDownLatch latch = new CountDownLatch(1);
@@ -195,12 +222,9 @@
     }
 
     @Test
-    public void testNetworkAttributes() {
+    public void testNetworkAttributes() throws UnknownHostException {
         final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        try {
-            na.setAssignedV4Address(
-                    (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
-        } catch (UnknownHostException e) { /* Can't happen */ }
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
         na.setGroupHint("hint1");
         na.setMtu(219);
         final String l2Key = FAKE_KEYS[0];
@@ -218,10 +242,8 @@
                         })));
 
         final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
-        try {
-            na.setDnsAddresses(Arrays.asList(
-                    new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
-        } catch (UnknownHostException e) { /* Still can't happen */ }
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
         final NetworkAttributes attributes2 = na2.build();
         storeAttributes("Did not complete storing attributes 2", l2Key, attributes2);
 
@@ -333,8 +355,93 @@
     }
 
     @Test
-    public void testFindL2Key() {
-        // TODO : implement this
+    public void testFindL2Key() throws UnknownHostException {
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+        na.setGroupHint("hint0");
+        storeAttributes(FAKE_KEYS[0], na.build());
+
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
+        na.setMtu(219);
+        storeAttributes(FAKE_KEYS[1], na.build());
+        na.setMtu(null);
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setDnsAddresses(Arrays.asList(
+                new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+        na.setGroupHint("hint1");
+        storeAttributes(FAKE_KEYS[2], na.build());
+        na.setMtu(219);
+        storeAttributes(FAKE_KEYS[3], na.build());
+        na.setMtu(240);
+        storeAttributes(FAKE_KEYS[4], na.build());
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
+        storeAttributes(FAKE_KEYS[5], na.build());
+
+        // Matches key 5 exactly
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[5], key);
+                })));
+
+        // MTU matches key 4 but v4 address matches key 5. The latter is stronger.
+        na.setMtu(240);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[5], key);
+                })));
+
+        // Closest to key 3 (indeed, identical)
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        na.setMtu(219);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[3], key);
+                })));
+
+        // Group hint alone must not be strong enough to override the rest
+        na.setGroupHint("hint0");
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[3], key);
+                })));
+
+        // Still closest to key 3, though confidence is lower
+        na.setGroupHint("hint1");
+        na.setDnsAddresses(null);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[3], key);
+                })));
+
+        // But changing the MTU makes this closer to key 4
+        na.setMtu(240);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(FAKE_KEYS[4], key);
+                })));
+
+        // MTU alone not strong enough to make this group-close
+        na.setGroupHint(null);
+        na.setDnsAddresses(null);
+        na.setAssignedV4Address(null);
+        doLatched("Did not finish finding L2Key", latch ->
+                mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertNull(key);
+                })));
     }
 
     private void assertNetworksSameness(final String key1, final String key2, final int sameness) {
@@ -349,7 +456,7 @@
     @Test
     public void testIsSameNetwork() throws UnknownHostException {
         final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
+        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
         na.setGroupHint("hint1");
         na.setMtu(219);
         na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));