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