Home | History | Annotate | Download | only in connectivity
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.connectivity;
     18 
     19 import android.net.InterfaceConfiguration;
     20 import android.net.ConnectivityManager;
     21 import android.net.LinkAddress;
     22 import android.net.LinkProperties;
     23 import android.net.RouteInfo;
     24 import android.os.INetworkManagementService;
     25 import android.os.RemoteException;
     26 import android.util.Slog;
     27 
     28 import com.android.internal.util.ArrayUtils;
     29 import com.android.server.net.BaseNetworkObserver;
     30 
     31 import java.net.Inet4Address;
     32 import java.util.Objects;
     33 
     34 /**
     35  * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated
     36  * from a consistent and unique thread context. It is the responsibility of ConnectivityService to
     37  * call into this class from its own Handler thread.
     38  *
     39  * @hide
     40  */
     41 public class Nat464Xlat extends BaseNetworkObserver {
     42     private static final String TAG = Nat464Xlat.class.getSimpleName();
     43 
     44     // This must match the interface prefix in clatd.c.
     45     private static final String CLAT_PREFIX = "v4-";
     46 
     47     // The network types we will start clatd on,
     48     // allowing clat only on networks for which we can support IPv6-only.
     49     private static final int[] NETWORK_TYPES = {
     50             ConnectivityManager.TYPE_MOBILE,
     51             ConnectivityManager.TYPE_WIFI,
     52             ConnectivityManager.TYPE_ETHERNET,
     53     };
     54 
     55     private final INetworkManagementService mNMService;
     56 
     57     // The network we're running on, and its type.
     58     private final NetworkAgentInfo mNetwork;
     59 
     60     private enum State {
     61         IDLE,       // start() not called. Base iface and stacked iface names are null.
     62         STARTING,   // start() called. Base iface and stacked iface names are known.
     63         RUNNING,    // start() called, and the stacked iface is known to be up.
     64         STOPPING;   // stop() called, this Nat464Xlat is still registered as a network observer for
     65                     // the stacked interface.
     66     }
     67 
     68     private String mBaseIface;
     69     private String mIface;
     70     private State mState = State.IDLE;
     71 
     72     public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
     73         mNMService = nmService;
     74         mNetwork = nai;
     75     }
     76 
     77     /**
     78      * Determines whether a network requires clat.
     79      * @param network the NetworkAgentInfo corresponding to the network.
     80      * @return true if the network requires clat, false otherwise.
     81      */
     82     public static boolean requiresClat(NetworkAgentInfo nai) {
     83         // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
     84         final int netType = nai.networkInfo.getType();
     85         final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
     86         // TODO: this should also consider if the network is in SUSPENDED state to avoid stopping
     87         // clatd in SUSPENDED state.
     88         final boolean connected = nai.networkInfo.isConnected();
     89         // We only run clat on networks that don't have a native IPv4 address.
     90         final boolean hasIPv4Address =
     91                 (nai.linkProperties != null) && nai.linkProperties.hasIPv4Address();
     92         return supported && connected && !hasIPv4Address;
     93     }
     94 
     95     /**
     96      * @return true if clatd has been started and has not yet stopped.
     97      * A true result corresponds to internal states STARTING and RUNNING.
     98      */
     99     public boolean isStarted() {
    100         return mState != State.IDLE;
    101     }
    102 
    103     /**
    104      * @return true if clatd has been started but the stacked interface is not yet up.
    105      */
    106     public boolean isStarting() {
    107         return mState == State.STARTING;
    108     }
    109 
    110     /**
    111      * @return true if clatd has been started and the stacked interface is up.
    112      */
    113     public boolean isRunning() {
    114         return mState == State.RUNNING;
    115     }
    116 
    117     /**
    118      * @return true if clatd has been stopped.
    119      */
    120     public boolean isStopping() {
    121         return mState == State.STOPPING;
    122     }
    123 
    124     /**
    125      * Start clatd, register this Nat464Xlat as a network observer for the stacked interface,
    126      * and set internal state.
    127      */
    128     private void enterStartingState(String baseIface) {
    129         try {
    130             mNMService.registerObserver(this);
    131         } catch(RemoteException e) {
    132             Slog.e(TAG,
    133                     "startClat: Can't register interface observer for clat on " + mNetwork.name());
    134             return;
    135         }
    136         try {
    137             mNMService.startClatd(baseIface);
    138         } catch(RemoteException|IllegalStateException e) {
    139             Slog.e(TAG, "Error starting clatd on " + baseIface, e);
    140         }
    141         mIface = CLAT_PREFIX + baseIface;
    142         mBaseIface = baseIface;
    143         mState = State.STARTING;
    144     }
    145 
    146     /**
    147      * Enter running state just after getting confirmation that the stacked interface is up, and
    148      * turn ND offload off if on WiFi.
    149      */
    150     private void enterRunningState() {
    151         maybeSetIpv6NdOffload(mBaseIface, false);
    152         mState = State.RUNNING;
    153     }
    154 
    155     /**
    156      * Stop clatd, and turn ND offload on if it had been turned off.
    157      */
    158     private void enterStoppingState() {
    159         if (isRunning()) {
    160             maybeSetIpv6NdOffload(mBaseIface, true);
    161         }
    162 
    163         try {
    164             mNMService.stopClatd(mBaseIface);
    165         } catch(RemoteException|IllegalStateException e) {
    166             Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
    167         }
    168 
    169         mState = State.STOPPING;
    170     }
    171 
    172     /**
    173      * Unregister as a base observer for the stacked interface, and clear internal state.
    174      */
    175     private void enterIdleState() {
    176         try {
    177             mNMService.unregisterObserver(this);
    178         } catch(RemoteException|IllegalStateException e) {
    179             Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface, e);
    180         }
    181 
    182         mIface = null;
    183         mBaseIface = null;
    184         mState = State.IDLE;
    185     }
    186 
    187     /**
    188      * Starts the clat daemon.
    189      */
    190     public void start() {
    191         if (isStarted()) {
    192             Slog.e(TAG, "startClat: already started");
    193             return;
    194         }
    195 
    196         if (mNetwork.linkProperties == null) {
    197             Slog.e(TAG, "startClat: Can't start clat with null LinkProperties");
    198             return;
    199         }
    200 
    201         String baseIface = mNetwork.linkProperties.getInterfaceName();
    202         if (baseIface == null) {
    203             Slog.e(TAG, "startClat: Can't start clat on null interface");
    204             return;
    205         }
    206         // TODO: should we only do this if mNMService.startClatd() succeeds?
    207         Slog.i(TAG, "Starting clatd on " + baseIface);
    208         enterStartingState(baseIface);
    209     }
    210 
    211     /**
    212      * Stops the clat daemon.
    213      */
    214     public void stop() {
    215         if (!isStarted()) {
    216             return;
    217         }
    218         Slog.i(TAG, "Stopping clatd on " + mBaseIface);
    219 
    220         boolean wasStarting = isStarting();
    221         enterStoppingState();
    222         if (wasStarting) {
    223             enterIdleState();
    224         }
    225     }
    226 
    227     /**
    228      * Copies the stacked clat link in oldLp, if any, to the LinkProperties in mNetwork.
    229      * This is necessary because the LinkProperties in mNetwork come from the transport layer, which
    230      * has no idea that 464xlat is running on top of it.
    231      */
    232     public void fixupLinkProperties(LinkProperties oldLp) {
    233         if (!isRunning()) {
    234             return;
    235         }
    236         LinkProperties lp = mNetwork.linkProperties;
    237         if (lp == null || lp.getAllInterfaceNames().contains(mIface)) {
    238             return;
    239         }
    240 
    241         Slog.d(TAG, "clatd running, updating NAI for " + mIface);
    242         for (LinkProperties stacked: oldLp.getStackedLinks()) {
    243             if (Objects.equals(mIface, stacked.getInterfaceName())) {
    244                 lp.addStackedLink(stacked);
    245                 return;
    246             }
    247         }
    248     }
    249 
    250     private LinkProperties makeLinkProperties(LinkAddress clatAddress) {
    251         LinkProperties stacked = new LinkProperties();
    252         stacked.setInterfaceName(mIface);
    253 
    254         // Although the clat interface is a point-to-point tunnel, we don't
    255         // point the route directly at the interface because some apps don't
    256         // understand routes without gateways (see, e.g., http://b/9597256
    257         // http://b/9597516). Instead, set the next hop of the route to the
    258         // clat IPv4 address itself (for those apps, it doesn't matter what
    259         // the IP of the gateway is, only that there is one).
    260         RouteInfo ipv4Default = new RouteInfo(
    261                 new LinkAddress(Inet4Address.ANY, 0),
    262                 clatAddress.getAddress(), mIface);
    263         stacked.addRoute(ipv4Default);
    264         stacked.addLinkAddress(clatAddress);
    265         return stacked;
    266     }
    267 
    268     private LinkAddress getLinkAddress(String iface) {
    269         try {
    270             InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
    271             return config.getLinkAddress();
    272         } catch(RemoteException|IllegalStateException e) {
    273             Slog.e(TAG, "Error getting link properties: " + e);
    274             return null;
    275         }
    276     }
    277 
    278     private void maybeSetIpv6NdOffload(String iface, boolean on) {
    279         // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
    280         if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
    281             return;
    282         }
    283         try {
    284             Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
    285             mNMService.setInterfaceIpv6NdOffload(iface, on);
    286         } catch(RemoteException|IllegalStateException e) {
    287             Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
    288         }
    289     }
    290 
    291     /**
    292      * Adds stacked link on base link and transitions to RUNNING state.
    293      */
    294     private void handleInterfaceLinkStateChanged(String iface, boolean up) {
    295         if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
    296             return;
    297         }
    298 
    299         LinkAddress clatAddress = getLinkAddress(iface);
    300         if (clatAddress == null) {
    301             Slog.e(TAG, "clatAddress was null for stacked iface " + iface);
    302             return;
    303         }
    304 
    305         Slog.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s",
    306                 mIface, mIface, mBaseIface));
    307         enterRunningState();
    308         LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
    309         lp.addStackedLink(makeLinkProperties(clatAddress));
    310         mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
    311     }
    312 
    313     /**
    314      * Removes stacked link on base link and transitions to IDLE state.
    315      */
    316     private void handleInterfaceRemoved(String iface) {
    317         if (!Objects.equals(mIface, iface)) {
    318             return;
    319         }
    320         if (!isRunning() && !isStopping()) {
    321             return;
    322         }
    323 
    324         Slog.i(TAG, "interface " + iface + " removed");
    325         if (!isStopping()) {
    326             // Ensure clatd is stopped if stop() has not been called: this likely means that clatd
    327             // has crashed.
    328             enterStoppingState();
    329         }
    330         enterIdleState();
    331         LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
    332         lp.removeStackedLink(iface);
    333         mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
    334     }
    335 
    336     @Override
    337     public void interfaceLinkStateChanged(String iface, boolean up) {
    338         mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); });
    339     }
    340 
    341     @Override
    342     public void interfaceRemoved(String iface) {
    343         mNetwork.handler().post(() -> { handleInterfaceRemoved(iface); });
    344     }
    345 
    346     @Override
    347     public String toString() {
    348         return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
    349     }
    350 }
    351