Add IPv6 tethering coordinator

Add an IPv6TetheringCoordinator to TetheringMaster StateMachine, which
receives and processes NetworkState updates and passes the necessary IPv6
information to the revelant TetherInterfaceStateMachine.

Add an IPv6TetheringInterfaceServices to TetherInterfaceStateMachine, which
is responsible for adding local network routes and managing an IPv6
RouterAdvertisementDaemon.

Bug: 9580643
Change-Id: I3eaae460b80752e2115359d7bde873a1e9ea515a
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index 384ab1c..6e74f14 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -103,7 +103,7 @@
     private boolean isIPv6ULA() {
         if (address != null && address instanceof Inet6Address) {
             byte[] bytes = address.getAddress();
-            return ((bytes[0] & (byte)0xfc) == (byte)0xfc);
+            return ((bytes[0] & (byte)0xfe) == (byte)0xfc);
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 6f67b6f..92b55db 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -69,6 +69,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.IoThread;
 import com.android.server.connectivity.tethering.IControlsTethering;
+import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
 import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
 import com.android.server.net.BaseNetworkObserver;
 
@@ -1143,7 +1144,8 @@
         // Because we excise interfaces immediately from mTetherStates, we must maintain mNotifyList
         // so that the garbage collector does not clean up the state machine before it has a chance
         // to tear itself down.
-        private ArrayList<TetherInterfaceStateMachine> mNotifyList;
+        private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
+        private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
 
         private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
         private NetworkCallback mMobileUpstreamCallback;
@@ -1171,6 +1173,7 @@
             addState(mSetDnsForwardersErrorState);
 
             mNotifyList = new ArrayList<>();
+            mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList);
             setInitialState(mInitialState);
         }
 
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
new file mode 100644
index 0000000..8254397
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.net.ConnectivityManager;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkState;
+import android.net.RouteInfo;
+import android.util.Log;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+
+
+/**
+ * IPv6 tethering is rather different from IPv4 owing to the absence of NAT.
+ * This coordinator is responsible for evaluating the dedicated prefixes
+ * assigned to the device and deciding how to divvy them up among downstream
+ * interfaces.
+ *
+ * @hide
+ */
+public class IPv6TetheringCoordinator {
+    private static final String TAG = IPv6TetheringCoordinator.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
+    private NetworkState mUpstreamNetworkState;
+
+    public IPv6TetheringCoordinator(ArrayList<TetherInterfaceStateMachine> notifyList) {
+        mNotifyList = notifyList;
+    }
+
+    public void updateUpstreamNetworkState(NetworkState ns) {
+        if (VDBG) {
+            Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns));
+        }
+        if (ns == null || ns.network == null) {
+            stopIPv6TetheringOnAllInterfaces();
+            setUpstreamNetworkState(null);
+            return;
+        }
+
+        if (mUpstreamNetworkState != null &&
+            !ns.network.equals(mUpstreamNetworkState.network)) {
+            stopIPv6TetheringOnAllInterfaces();
+        }
+        setUpstreamNetworkState(ns);
+        maybeUpdateIPv6TetheringInterfaces();
+    }
+
+    private void stopIPv6TetheringOnAllInterfaces() {
+        for (TetherInterfaceStateMachine sm : mNotifyList) {
+            sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE,
+                    0, 0, null);
+        }
+    }
+
+    private void setUpstreamNetworkState(NetworkState ns) {
+        if (!canTetherIPv6(ns)) {
+            mUpstreamNetworkState = null;
+        } else {
+            mUpstreamNetworkState = new NetworkState(
+                    null,
+                    new LinkProperties(ns.linkProperties),
+                    new NetworkCapabilities(ns.networkCapabilities),
+                    new Network(ns.network),
+                    null,
+                    null);
+        }
+
+        if (DBG) {
+            Log.d(TAG, "setUpstreamNetworkState: " + toDebugString(mUpstreamNetworkState));
+        }
+    }
+
+    private void maybeUpdateIPv6TetheringInterfaces() {
+        if (mUpstreamNetworkState == null) return;
+
+        for (TetherInterfaceStateMachine sm : mNotifyList) {
+            final LinkProperties lp = getInterfaceIPv6LinkProperties(sm.interfaceType());
+            if (lp != null) {
+                sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, lp);
+            }
+            break;
+        }
+    }
+
+    private LinkProperties getInterfaceIPv6LinkProperties(int interfaceType) {
+        // NOTE: Here, in future, we would have policies to decide how to divvy
+        // up the available dedicated prefixes among downstream interfaces.
+        // At this time we have no such mechanism--we only support tethering
+        // IPv6 toward Wi-Fi interfaces.
+
+        switch (interfaceType) {
+            case ConnectivityManager.TETHERING_WIFI:
+                final LinkProperties lp = getIPv6OnlyLinkProperties(
+                        mUpstreamNetworkState.linkProperties);
+                if (lp.hasIPv6DefaultRoute() && lp.hasGlobalIPv6Address()) {
+                    return lp;
+                }
+                break;
+        }
+
+        return null;
+    }
+
+    private static boolean canTetherIPv6(NetworkState ns) {
+        // Broadly speaking:
+        //
+        //     [1] does the upstream have an IPv6 default route?
+        //
+        // and
+        //
+        //     [2] does the upstream have one or more global IPv6 /64s
+        //         dedicated to this device?
+        //
+        // In lieu of Prefix Delegation and other evaluation of whether a
+        // prefix may or may not be dedicated to this device, for now just
+        // check whether the upstream is TRANSPORT_CELLULAR. This works
+        // because "[t]he 3GPP network allocates each default bearer a unique
+        // /64 prefix", per RFC 6459, Section 5.2.
+
+        final boolean canTether =
+                (ns != null) && (ns.network != null) &&
+                (ns.linkProperties != null) && (ns.networkCapabilities != null) &&
+                // At least one upstream DNS server:
+                ns.linkProperties.isProvisioned() &&
+                // Minimal amount of IPv6 provisioning:
+                ns.linkProperties.hasIPv6DefaultRoute() &&
+                ns.linkProperties.hasGlobalIPv6Address() &&
+                // Temporary approximation of "dedicated prefix":
+                ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
+
+        // For now, we do not support separate IPv4 and IPv6 upstreams (e.g.
+        // tethering with 464xlat involved). TODO: Rectify this shortcoming,
+        // likely by calling NetworkManagementService#startInterfaceForwarding()
+        // for all upstream interfaces.
+        RouteInfo v4default = null;
+        RouteInfo v6default = null;
+        if (canTether) {
+            for (RouteInfo r : ns.linkProperties.getAllRoutes()) {
+                if (r.isIPv4Default()) {
+                    v4default = r;
+                } else if (r.isIPv6Default()) {
+                    v6default = r;
+                }
+
+                if (v4default != null && v6default != null) {
+                    break;
+                }
+            }
+        }
+
+        final boolean supportedConfiguration =
+                (v4default != null) && (v6default != null) &&
+                (v4default.getInterface() != null) &&
+                v4default.getInterface().equals(v6default.getInterface());
+
+        final boolean outcome = canTether && supportedConfiguration;
+
+        if (VDBG) {
+            if (ns == null) {
+                Log.d(TAG, "No available upstream.");
+            } else {
+                Log.d(TAG, String.format("IPv6 tethering is %s for upstream: %s",
+                        (outcome ? "available" : "not available"), toDebugString(ns)));
+            }
+        }
+
+        return outcome;
+    }
+
+    private static LinkProperties getIPv6OnlyLinkProperties(LinkProperties lp) {
+        final LinkProperties v6only = new LinkProperties();
+        if (lp == null) {
+            return v6only;
+        }
+
+        // NOTE: At this time we don't copy over any information about any
+        // stacked links. No current stacked link configuration has IPv6.
+
+        v6only.setInterfaceName(lp.getInterfaceName());
+
+        v6only.setMtu(lp.getMtu());
+
+        for (LinkAddress linkAddr : lp.getLinkAddresses()) {
+            if (linkAddr.isGlobalPreferred() && linkAddr.getPrefixLength() == 64) {
+                v6only.addLinkAddress(linkAddr);
+            }
+        }
+
+        for (RouteInfo routeInfo : lp.getRoutes()) {
+            final IpPrefix destination = routeInfo.getDestination();
+            if ((destination.getAddress() instanceof Inet6Address) &&
+                (destination.getPrefixLength() <= 64)) {
+                v6only.addRoute(routeInfo);
+            }
+        }
+
+        for (InetAddress dnsServer : lp.getDnsServers()) {
+            if (isIPv6GlobalAddress(dnsServer)) {
+                // For now we include ULAs.
+                v6only.addDnsServer(dnsServer);
+            }
+        }
+
+        v6only.setDomains(lp.getDomains());
+
+        return v6only;
+    }
+
+    // TODO: Delete this and switch to LinkAddress#isGlobalPreferred once we
+    // announce our own IPv6 address as DNS server.
+    private static boolean isIPv6GlobalAddress(InetAddress ip) {
+        return (ip instanceof Inet6Address) &&
+               !ip.isAnyLocalAddress() &&
+               !ip.isLoopbackAddress() &&
+               !ip.isLinkLocalAddress() &&
+               !ip.isSiteLocalAddress() &&
+               !ip.isMulticastAddress();
+    }
+
+    private static String toDebugString(NetworkState ns) {
+        if (ns == null) {
+            return "NetworkState{null}";
+        }
+        return String.format("NetworkState{%s, %s, %s}",
+                ns.network,
+                ns.networkCapabilities,
+                ns.linkProperties);
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
new file mode 100644
index 0000000..b742838
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.NetworkState;
+import android.net.RouteInfo;
+import android.net.ip.RouterAdvertisementDaemon;
+import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+
+/**
+ * @hide
+ */
+class IPv6TetheringInterfaceServices {
+    private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName();
+
+    private final String mIfName;
+    private final INetworkManagementService mNMService;
+
+    private NetworkInterface mNetworkInterface;
+    private byte[] mHwAddr;
+    private ArrayList<RouteInfo> mLastLocalRoutes;
+    private RouterAdvertisementDaemon mRaDaemon;
+    private RaParams mLastRaParams;
+
+    IPv6TetheringInterfaceServices(String ifname, INetworkManagementService nms) {
+        mIfName = ifname;
+        mNMService = nms;
+    }
+
+    public boolean start() {
+        try {
+            mNetworkInterface = NetworkInterface.getByName(mIfName);
+        } catch (SocketException e) {
+            Log.e(TAG, "Failed to find NetworkInterface for " + mIfName, e);
+            stop();
+            return false;
+        }
+
+        try {
+            mHwAddr = mNetworkInterface.getHardwareAddress();
+        } catch (SocketException e) {
+            Log.e(TAG, "Failed to find hardware address for " + mIfName, e);
+            stop();
+            return false;
+        }
+
+        final int ifindex = mNetworkInterface.getIndex();
+        mRaDaemon = new RouterAdvertisementDaemon(mIfName, ifindex, mHwAddr);
+        if (!mRaDaemon.start()) {
+            stop();
+            return false;
+        }
+
+        return true;
+    }
+
+    public void stop() {
+        mNetworkInterface = null;
+        mHwAddr = null;
+        updateLocalRoutes(null);
+        updateRaParams(null);
+
+        if (mRaDaemon != null) {
+            mRaDaemon.stop();
+            mRaDaemon = null;
+        }
+    }
+
+    // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
+    // LinkProperties. These have extraneous data filtered out and only the
+    // necessary prefixes included (per its prefix distribution policy).
+    //
+    // TODO: Evaluate using a data structure than is more directly suited to
+    // communicating only the relevant information.
+    public void updateUpstreamIPv6LinkProperties(LinkProperties v6only) {
+        if (mRaDaemon == null) return;
+
+        if (v6only == null) {
+            updateLocalRoutes(null);
+            updateRaParams(null);
+            return;
+        }
+
+        RaParams params = new RaParams();
+        params.mtu = v6only.getMtu();
+        params.hasDefaultRoute = v6only.hasIPv6DefaultRoute();
+
+        ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
+        for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
+            final IpPrefix prefix = new IpPrefix(linkAddr.getAddress(),
+                                                 linkAddr.getPrefixLength());
+
+            // Accumulate routes representing "prefixes to be assigned to the
+            // local interface", for subsequent addition to the local network
+            // in the routing rules.
+            localRoutes.add(new RouteInfo(prefix, null, mIfName));
+
+            params.prefixes.add(prefix);
+        }
+
+        // We need to be able to send unicast RAs, and clients might like to
+        // ping the default router's link-local address, so add that as well.
+        localRoutes.add(new RouteInfo(new IpPrefix("fe80::/64"), null, mIfName));
+
+        // TODO: Add a local interface address, update dnsmasq to listen on the
+        // new address, and use only that address as a DNS server.
+        for (InetAddress dnsServer : v6only.getDnsServers()) {
+            if (dnsServer instanceof Inet6Address) {
+                params.dnses.add((Inet6Address) dnsServer);
+            }
+        }
+
+        updateLocalRoutes(localRoutes);
+        updateRaParams(params);
+    }
+
+    private void updateLocalRoutes(ArrayList<RouteInfo> localRoutes) {
+        if (localRoutes != null) {
+            // TODO: Compare with mLastLocalRoutes and take appropriate
+            // appropriate action on the difference between the two.
+
+            if (!localRoutes.isEmpty()) {
+                try {
+                    mNMService.addInterfaceToLocalNetwork(mIfName, localRoutes);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to add IPv6 routes to local table: ", e);
+                }
+            }
+        } else {
+            if (mLastLocalRoutes != null && !mLastLocalRoutes.isEmpty()) {
+                // TODO: Remove only locally added network routes.
+                // mNMSwervice.removeInterfaceFromLocalNetwork(mIfName);
+            }
+        }
+
+        mLastLocalRoutes = localRoutes;
+    }
+
+    private void updateRaParams(RaParams params) {
+        if (mRaDaemon != null) {
+            // Currently, we send spurious RAs (5) whenever there's any update.
+            // TODO: Compare params with mLastParams to avoid spurious updates.
+            mRaDaemon.buildNewRa(params);
+        }
+
+        mLastRaParams = params;
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index aebeb69..9e7cb93 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -20,6 +20,7 @@
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.NetworkUtils;
 import android.os.INetworkManagementService;
 import android.os.Looper;
@@ -73,6 +74,8 @@
     public static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
     // the upstream connection has changed
     public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
+    // new IPv6 tethering parameters need to be processed
+    public static final int CMD_IPV6_TETHER_UPDATE          = BASE_IFACE + 13;
 
     private final State mInitialState;
     private final State mTetheredState;
@@ -84,6 +87,7 @@
 
     private final String mIfaceName;
     private final int mInterfaceType;
+    private final IPv6TetheringInterfaceServices mIPv6TetherSvc;
 
     private int mLastError;
     private String mMyUpstreamIfaceName;  // may change over time
@@ -97,6 +101,7 @@
         mTetherController = tetherController;
         mIfaceName = ifaceName;
         mInterfaceType = interfaceType;
+        mIPv6TetherSvc = new IPv6TetheringInterfaceServices(mIfaceName, mNMService);
         mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
 
         mInitialState = new InitialState();
@@ -109,6 +114,10 @@
         setInitialState(mInitialState);
     }
 
+    public int interfaceType() {
+        return mInterfaceType;
+    }
+
     // configured when we start tethering and unconfig'd on error or conclusion
     private boolean configureIfaceIp(boolean enabled) {
         if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
@@ -175,6 +184,10 @@
                 case CMD_INTERFACE_DOWN:
                     transitionTo(mUnavailableState);
                     break;
+                case CMD_IPV6_TETHER_UPDATE:
+                    mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
+                            (LinkProperties) message.obj);
+                    break;
                 default:
                     retValue = false;
                     break;
@@ -200,6 +213,11 @@
                 transitionTo(mInitialState);
                 return;
             }
+
+            if (!mIPv6TetherSvc.start()) {
+                Log.e(TAG, "Failed to start IPv6TetheringInterfaceServices");
+            }
+
             if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
             mTetherController.notifyInterfaceStateChange(
                     mIfaceName, TetherInterfaceStateMachine.this,
@@ -211,6 +229,7 @@
             // Note that at this point, we're leaving the tethered state.  We can fail any
             // of these operations, but it doesn't really change that we have to try them
             // all in sequence.
+            mIPv6TetherSvc.stop();
             cleanupUpstream();
 
             try {
@@ -287,6 +306,10 @@
                     }
                     mMyUpstreamIfaceName = newUpstreamIfaceName;
                     break;
+                case CMD_IPV6_TETHER_UPDATE:
+                    mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
+                            (LinkProperties) message.obj);
+                    break;
                 case CMD_IP_FORWARDING_ENABLE_ERROR:
                 case CMD_IP_FORWARDING_DISABLE_ERROR:
                 case CMD_START_TETHERING_ERROR: