Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 170a58bd authored by Erik Kline's avatar Erik Kline Committed by android-build-merger
Browse files

Add IPv6 tethering coordinator am: 1eb8c69b

am: 5b429166

Change-Id: I9a5bd7d72ec6a4b63922fc5704d15cb5eafc1e8d
parents efe18d23 5b429166
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -103,7 +103,7 @@ public class LinkAddress implements Parcelable {
    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;
    }
+4 −1
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ import com.android.internal.util.State;
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 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        // 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 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            addState(mSetDnsForwardersErrorState);

            mNotifyList = new ArrayList<>();
            mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList);
            setInitialState(mInitialState);
        }

+253 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+177 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+23 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.net.ConnectivityManager;
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 class TetherInterfaceStateMachine extends StateMachine {
    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 @@ public class TetherInterfaceStateMachine extends StateMachine {

    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 @@ public class TetherInterfaceStateMachine extends StateMachine {
        mTetherController = tetherController;
        mIfaceName = ifaceName;
        mInterfaceType = interfaceType;
        mIPv6TetherSvc = new IPv6TetheringInterfaceServices(mIfaceName, mNMService);
        mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;

        mInitialState = new InitialState();
@@ -109,6 +114,10 @@ public class TetherInterfaceStateMachine extends StateMachine {
        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 @@ public class TetherInterfaceStateMachine extends StateMachine {
                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 @@ public class TetherInterfaceStateMachine extends StateMachine {
                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 @@ public class TetherInterfaceStateMachine extends StateMachine {
            // 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 @@ public class TetherInterfaceStateMachine extends StateMachine {
                    }
                    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: