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 java.net.Inet4Address;
     20 
     21 import android.content.Context;
     22 import android.net.InterfaceConfiguration;
     23 import android.net.ConnectivityManager;
     24 import android.net.LinkAddress;
     25 import android.net.LinkProperties;
     26 import android.net.NetworkAgent;
     27 import android.net.RouteInfo;
     28 import android.os.Handler;
     29 import android.os.Message;
     30 import android.os.INetworkManagementService;
     31 import android.os.RemoteException;
     32 import android.util.Slog;
     33 
     34 import com.android.server.net.BaseNetworkObserver;
     35 import com.android.internal.util.ArrayUtils;
     36 
     37 /**
     38  * @hide
     39  *
     40  * Class to manage a 464xlat CLAT daemon.
     41  */
     42 public class Nat464Xlat extends BaseNetworkObserver {
     43     private static final String TAG = "Nat464Xlat";
     44 
     45     // This must match the interface prefix in clatd.c.
     46     private static final String CLAT_PREFIX = "v4-";
     47 
     48     // The network types we will start clatd on.
     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     // ConnectivityService Handler for LinkProperties updates.
     58     private final Handler mHandler;
     59 
     60     // The network we're running on, and its type.
     61     private final NetworkAgentInfo mNetwork;
     62 
     63     // Internal state variables.
     64     //
     65     // The possible states are:
     66     //  - Idle: start() not called. Everything is null.
     67     //  - Starting: start() called. Interfaces are non-null. isStarted() returns true.
     68     //    mIsRunning is false.
     69     //  - Running: start() called, and interfaceLinkStateChanged() told us that mIface is up.
     70     //    mIsRunning is true.
     71     //
     72     // Once mIface is non-null and isStarted() is true, methods called by ConnectivityService on
     73     // its handler thread must not modify any internal state variables; they are only updated by the
     74     // interface observers, called on the notification threads.
     75     private String mBaseIface;
     76     private String mIface;
     77     private boolean mIsRunning;
     78 
     79     public Nat464Xlat(
     80             Context context, INetworkManagementService nmService,
     81             Handler handler, NetworkAgentInfo nai) {
     82         mNMService = nmService;
     83         mHandler = handler;
     84         mNetwork = nai;
     85     }
     86 
     87     /**
     88      * Determines whether a network requires clat.
     89      * @param network the NetworkAgentInfo corresponding to the network.
     90      * @return true if the network requires clat, false otherwise.
     91      */
     92     public static boolean requiresClat(NetworkAgentInfo nai) {
     93         final int netType = nai.networkInfo.getType();
     94         final boolean connected = nai.networkInfo.isConnected();
     95         final boolean hasIPv4Address =
     96                 (nai.linkProperties != null) ? nai.linkProperties.hasIPv4Address() : false;
     97         // Only support clat on mobile and wifi for now, because these are the only IPv6-only
     98         // networks we can connect to.
     99         return connected && !hasIPv4Address && ArrayUtils.contains(NETWORK_TYPES, netType);
    100     }
    101 
    102     /**
    103      * Determines whether clatd is started. Always true, except a) if start has not yet been called,
    104      * or b) if our interface was removed.
    105      */
    106     public boolean isStarted() {
    107         return mIface != null;
    108     }
    109 
    110     /**
    111      * Clears internal state. Must not be called by ConnectivityService.
    112      */
    113     private void clear() {
    114         mIface = null;
    115         mBaseIface = null;
    116         mIsRunning = false;
    117     }
    118 
    119     /**
    120      * Starts the clat daemon. Called by ConnectivityService on the handler thread.
    121      */
    122     public void start() {
    123         if (isStarted()) {
    124             Slog.e(TAG, "startClat: already started");
    125             return;
    126         }
    127 
    128         if (mNetwork.linkProperties == null) {
    129             Slog.e(TAG, "startClat: Can't start clat with null LinkProperties");
    130             return;
    131         }
    132 
    133         try {
    134             mNMService.registerObserver(this);
    135         } catch(RemoteException e) {
    136             Slog.e(TAG, "startClat: Can't register interface observer for clat on " + mNetwork);
    137             return;
    138         }
    139 
    140         mBaseIface = mNetwork.linkProperties.getInterfaceName();
    141         if (mBaseIface == null) {
    142             Slog.e(TAG, "startClat: Can't start clat on null interface");
    143             return;
    144         }
    145         mIface = CLAT_PREFIX + mBaseIface;
    146         // From now on, isStarted() will return true.
    147 
    148         Slog.i(TAG, "Starting clatd on " + mBaseIface);
    149         try {
    150             mNMService.startClatd(mBaseIface);
    151         } catch(RemoteException|IllegalStateException e) {
    152             Slog.e(TAG, "Error starting clatd: " + e);
    153         }
    154     }
    155 
    156     /**
    157      * Stops the clat daemon. Called by ConnectivityService on the handler thread.
    158      */
    159     public void stop() {
    160         if (isStarted()) {
    161             Slog.i(TAG, "Stopping clatd");
    162             try {
    163                 mNMService.stopClatd(mBaseIface);
    164             } catch(RemoteException|IllegalStateException e) {
    165                 Slog.e(TAG, "Error stopping clatd: " + e);
    166             }
    167             // When clatd stops and its interface is deleted, interfaceRemoved() will notify
    168             // ConnectivityService and call clear().
    169         } else {
    170             Slog.e(TAG, "clatd: already stopped");
    171         }
    172     }
    173 
    174     private void updateConnectivityService(LinkProperties lp) {
    175         Message msg = mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, lp);
    176         msg.replyTo = mNetwork.messenger;
    177         Slog.i(TAG, "sending message to ConnectivityService: " + msg);
    178         msg.sendToTarget();
    179     }
    180 
    181     /**
    182      * Copies the stacked clat link in oldLp, if any, to the LinkProperties in mNetwork.
    183      * This is necessary because the LinkProperties in mNetwork come from the transport layer, which
    184      * has no idea that 464xlat is running on top of it.
    185      */
    186     public void fixupLinkProperties(LinkProperties oldLp) {
    187         if (mNetwork.clatd != null &&
    188                 mIsRunning &&
    189                 mNetwork.linkProperties != null &&
    190                 !mNetwork.linkProperties.getAllInterfaceNames().contains(mIface)) {
    191             Slog.d(TAG, "clatd running, updating NAI for " + mIface);
    192             for (LinkProperties stacked: oldLp.getStackedLinks()) {
    193                 if (mIface.equals(stacked.getInterfaceName())) {
    194                     mNetwork.linkProperties.addStackedLink(stacked);
    195                     break;
    196                 }
    197             }
    198         }
    199     }
    200 
    201     private LinkProperties makeLinkProperties(LinkAddress clatAddress) {
    202         LinkProperties stacked = new LinkProperties();
    203         stacked.setInterfaceName(mIface);
    204 
    205         // Although the clat interface is a point-to-point tunnel, we don't
    206         // point the route directly at the interface because some apps don't
    207         // understand routes without gateways (see, e.g., http://b/9597256
    208         // http://b/9597516). Instead, set the next hop of the route to the
    209         // clat IPv4 address itself (for those apps, it doesn't matter what
    210         // the IP of the gateway is, only that there is one).
    211         RouteInfo ipv4Default = new RouteInfo(
    212                 new LinkAddress(Inet4Address.ANY, 0),
    213                 clatAddress.getAddress(), mIface);
    214         stacked.addRoute(ipv4Default);
    215         stacked.addLinkAddress(clatAddress);
    216         return stacked;
    217     }
    218 
    219     private LinkAddress getLinkAddress(String iface) {
    220         try {
    221             InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
    222             return config.getLinkAddress();
    223         } catch(RemoteException|IllegalStateException e) {
    224             Slog.e(TAG, "Error getting link properties: " + e);
    225             return null;
    226         }
    227     }
    228 
    229     private void maybeSetIpv6NdOffload(String iface, boolean on) {
    230         if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
    231             return;
    232         }
    233         try {
    234             Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
    235             mNMService.setInterfaceIpv6NdOffload(iface, on);
    236         } catch(RemoteException|IllegalStateException e) {
    237             Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
    238         }
    239     }
    240 
    241     @Override
    242     public void interfaceLinkStateChanged(String iface, boolean up) {
    243         // Called by the InterfaceObserver on its own thread, so can race with stop().
    244         if (isStarted() && up && mIface.equals(iface)) {
    245             Slog.i(TAG, "interface " + iface + " is up, mIsRunning " + mIsRunning + "->true");
    246 
    247             if (!mIsRunning) {
    248                 LinkAddress clatAddress = getLinkAddress(iface);
    249                 if (clatAddress == null) {
    250                     return;
    251                 }
    252                 mIsRunning = true;
    253                 maybeSetIpv6NdOffload(mBaseIface, false);
    254                 LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
    255                 lp.addStackedLink(makeLinkProperties(clatAddress));
    256                 Slog.i(TAG, "Adding stacked link " + mIface + " on top of " + mBaseIface);
    257                 updateConnectivityService(lp);
    258             }
    259         }
    260     }
    261 
    262     @Override
    263     public void interfaceRemoved(String iface) {
    264         if (isStarted() && mIface.equals(iface)) {
    265             Slog.i(TAG, "interface " + iface + " removed, mIsRunning " + mIsRunning + "->false");
    266 
    267             if (mIsRunning) {
    268                 // The interface going away likely means clatd has crashed. Ask netd to stop it,
    269                 // because otherwise when we try to start it again on the same base interface netd
    270                 // will complain that it's already started.
    271                 //
    272                 // Note that this method can be called by the interface observer at the same time
    273                 // that ConnectivityService calls stop(). In this case, the second call to
    274                 // stopClatd() will just throw IllegalStateException, which we'll ignore.
    275                 try {
    276                     mNMService.unregisterObserver(this);
    277                     mNMService.stopClatd(mBaseIface);
    278                 } catch (RemoteException|IllegalStateException e) {
    279                     // Well, we tried.
    280                 }
    281                 maybeSetIpv6NdOffload(mBaseIface, true);
    282                 LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
    283                 lp.removeStackedLink(mIface);
    284                 clear();
    285                 updateConnectivityService(lp);
    286             }
    287         }
    288     }
    289 }
    290