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