/* * 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; import java.util.LinkedList; /** * 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 mNotifyList; private final LinkedList mActiveDownstreams; private NetworkState mUpstreamNetworkState; public IPv6TetheringCoordinator(ArrayList notifyList) { mNotifyList = notifyList; mActiveDownstreams = new LinkedList<>(); } public void addActiveDownstream(TetherInterfaceStateMachine downstream) { if (mActiveDownstreams.indexOf(downstream) == -1) { // Adding a new downstream appends it to the list. Adding a // downstream a second time without first removing it has no effect. mActiveDownstreams.offer(downstream); updateIPv6TetheringInterfaces(); } } public void removeActiveDownstream(TetherInterfaceStateMachine downstream) { stopIPv6TetheringOn(downstream); if (mActiveDownstreams.remove(downstream)) { updateIPv6TetheringInterfaces(); } } public void updateUpstreamNetworkState(NetworkState ns) { if (VDBG) { Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns)); } if (!canTetherIPv6(ns)) { stopIPv6TetheringOnAllInterfaces(); setUpstreamNetworkState(null); return; } if (mUpstreamNetworkState != null && !ns.network.equals(mUpstreamNetworkState.network)) { stopIPv6TetheringOnAllInterfaces(); } setUpstreamNetworkState(ns); updateIPv6TetheringInterfaces(); } private void stopIPv6TetheringOnAllInterfaces() { for (TetherInterfaceStateMachine sm : mNotifyList) { stopIPv6TetheringOn(sm); } } private void setUpstreamNetworkState(NetworkState ns) { if (ns == null) { mUpstreamNetworkState = null; } else { // Make a deep copy of the parts we need. 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 updateIPv6TetheringInterfaces() { for (TetherInterfaceStateMachine sm : mNotifyList) { final LinkProperties lp = getInterfaceIPv6LinkProperties(sm); sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, lp); break; } } private LinkProperties getInterfaceIPv6LinkProperties(TetherInterfaceStateMachine sm) { if (mUpstreamNetworkState == null) return null; if (sm.interfaceType() == ConnectivityManager.TETHERING_BLUETOOTH) { // TODO: Figure out IPv6 support on PAN interfaces. return null; } // 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 the oldest (first requested) active downstream. final TetherInterfaceStateMachine currentActive = mActiveDownstreams.peek(); if (currentActive != null && currentActive == sm) { final LinkProperties lp = getIPv6OnlyLinkProperties( mUpstreamNetworkState.linkProperties); if (lp.hasIPv6DefaultRoute() && lp.hasGlobalIPv6Address()) { return lp; } } 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); } private static void stopIPv6TetheringOn(TetherInterfaceStateMachine sm) { sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, null); } }