/* * 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.net.util.NetworkConstants; import android.net.util.SharedLog; import android.util.Log; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.Random; /** * 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 static class Downstream { public final TetherInterfaceStateMachine tism; public final int mode; // IControlsTethering.STATE_* // Used to append to a ULA /48, constructing a ULA /64 for local use. public final short subnetId; Downstream(TetherInterfaceStateMachine tism, int mode, short subnetId) { this.tism = tism; this.mode = mode; this.subnetId = subnetId; } } private final ArrayList<TetherInterfaceStateMachine> mNotifyList; private final SharedLog mLog; // NOTE: mActiveDownstreams is a list and not a hash data structure because // we keep active downstreams in arrival order. This is done so /64s can // be parceled out on a "first come, first served" basis and a /64 used by // a downstream that is no longer active can be redistributed to any next // waiting active downstream (again, in arrival order). private final LinkedList<Downstream> mActiveDownstreams; private final byte[] mUniqueLocalPrefix; private short mNextSubnetId; private NetworkState mUpstreamNetworkState; public IPv6TetheringCoordinator(ArrayList<TetherInterfaceStateMachine> notifyList, SharedLog log) { mNotifyList = notifyList; mLog = log.forSubComponent(TAG); mActiveDownstreams = new LinkedList<>(); mUniqueLocalPrefix = generateUniqueLocalPrefix(); mNextSubnetId = 0; } public void addActiveDownstream(TetherInterfaceStateMachine downstream, int mode) { if (findDownstream(downstream) == null) { // Adding a new downstream appends it to the list. Adding a // downstream a second time without first removing it has no effect. // We never change the mode of a downstream except by first removing // it and then re-adding it (with its new mode specified); if (mActiveDownstreams.offer(new Downstream(downstream, mode, mNextSubnetId))) { // Make sure subnet IDs are always positive. They are appended // to a ULA /48 to make a ULA /64 for local use. mNextSubnetId = (short) Math.max(0, mNextSubnetId + 1); } updateIPv6TetheringInterfaces(); } } public void removeActiveDownstream(TetherInterfaceStateMachine downstream) { stopIPv6TetheringOn(downstream); if (mActiveDownstreams.remove(findDownstream(downstream))) { updateIPv6TetheringInterfaces(); } // When tethering is stopping we can reset the subnet counter. if (mNotifyList.isEmpty()) { if (!mActiveDownstreams.isEmpty()) { Log.wtf(TAG, "Tethering notify list empty, IPv6 downstreams non-empty."); } mNextSubnetId = 0; } } public void updateUpstreamNetworkState(NetworkState ns) { if (VDBG) { Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns)); } if (!canTetherIPv6(ns, mLog)) { 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); } mLog.log("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 (sm.interfaceType() == ConnectivityManager.TETHERING_BLUETOOTH) { // TODO: Figure out IPv6 support on PAN interfaces. return null; } final Downstream ds = findDownstream(sm); if (ds == null) return null; if (ds.mode == IControlsTethering.STATE_LOCAL_ONLY) { // Build a Unique Locally-assigned Prefix configuration. return getUniqueLocalConfig(mUniqueLocalPrefix, ds.subnetId); } // This downstream is in IControlsTethering.STATE_TETHERED mode. if (mUpstreamNetworkState == null || mUpstreamNetworkState.linkProperties == null) { 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 Downstream currentActive = mActiveDownstreams.peek(); if (currentActive != null && currentActive.tism == sm) { final LinkProperties lp = getIPv6OnlyLinkProperties( mUpstreamNetworkState.linkProperties); if (lp.hasIPv6DefaultRoute() && lp.hasGlobalIPv6Address()) { return lp; } } return null; } Downstream findDownstream(TetherInterfaceStateMachine tism) { for (Downstream ds : mActiveDownstreams) { if (ds.tism == tism) return ds; } return null; } private static boolean canTetherIPv6(NetworkState ns, SharedLog sharedLog) { // 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 (ns == null) { sharedLog.log("No available upstream."); } else { sharedLog.log(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 LinkProperties getUniqueLocalConfig(byte[] ulp, short subnetId) { final LinkProperties lp = new LinkProperties(); final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48); lp.addRoute(new RouteInfo(local48, null, null)); final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64); // Because this is a locally-generated ULA, we don't have an upstream // address. But because the downstream IP address management code gets // its prefix from the upstream's IP address, we create a fake one here. lp.addLinkAddress(new LinkAddress(local64.getAddress(), 64)); lp.setMtu(NetworkConstants.ETHER_MTU); return lp; } private static IpPrefix makeUniqueLocalPrefix(byte[] in6addr, short subnetId, int prefixlen) { final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length); bytes[7] = (byte) (subnetId >> 8); bytes[8] = (byte) subnetId; return new IpPrefix(bytes, prefixlen); } // Generates a Unique Locally-assigned Prefix: // // https://tools.ietf.org/html/rfc4193#section-3.1 // // The result is a /48 that can be used for local-only communications. private static byte[] generateUniqueLocalPrefix() { final byte[] ulp = new byte[6]; // 6 = 48bits / 8bits/byte (new Random()).nextBytes(ulp); final byte[] in6addr = Arrays.copyOf(ulp, NetworkConstants.IPV6_ADDR_LEN); in6addr[0] = (byte) 0xfd; // fc00::/7 and L=1 return in6addr; } 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); } }