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