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 
Nat464Xlat( Context context, INetworkManagementService nmService, Handler handler, NetworkAgentInfo nai)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      */
requiresClat(NetworkAgentInfo nai)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      */
isStarted()106     public boolean isStarted() {
107         return mIface != null;
108     }
109 
110     /**
111      * Clears internal state. Must not be called by ConnectivityService.
112      */
clear()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      */
start()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      */
stop()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 
updateConnectivityService(LinkProperties lp)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      */
fixupLinkProperties(LinkProperties oldLp)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 
makeLinkProperties(LinkAddress clatAddress)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 
getLinkAddress(String iface)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 
maybeSetIpv6NdOffload(String iface, boolean on)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
interfaceLinkStateChanged(String iface, boolean up)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
interfaceRemoved(String iface)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