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