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 android.net.InterfaceConfiguration;
20 import android.net.ConnectivityManager;
21 import android.net.LinkAddress;
22 import android.net.LinkProperties;
23 import android.net.NetworkInfo;
24 import android.net.RouteInfo;
25 import android.os.INetworkManagementService;
26 import android.os.RemoteException;
27 import android.util.Slog;
28 
29 import com.android.internal.util.ArrayUtils;
30 import com.android.server.net.BaseNetworkObserver;
31 
32 import java.net.Inet4Address;
33 import java.util.Objects;
34 
35 /**
36  * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated
37  * from a consistent and unique thread context. It is the responsibility of ConnectivityService to
38  * call into this class from its own Handler thread.
39  *
40  * @hide
41  */
42 public class Nat464Xlat extends BaseNetworkObserver {
43     private static final String TAG = Nat464Xlat.class.getSimpleName();
44 
45     // This must match the interface prefix in clatd.c.
46     private static final String CLAT_PREFIX = "v4-";
47 
48     // The network types on which we will start clatd,
49     // allowing clat only on networks for which we can support IPv6-only.
50     private static final int[] NETWORK_TYPES = {
51         ConnectivityManager.TYPE_MOBILE,
52         ConnectivityManager.TYPE_WIFI,
53         ConnectivityManager.TYPE_ETHERNET,
54     };
55 
56     // The network states in which running clatd is supported.
57     private static final NetworkInfo.State[] NETWORK_STATES = {
58         NetworkInfo.State.CONNECTED,
59         NetworkInfo.State.SUSPENDED,
60     };
61 
62     private final INetworkManagementService mNMService;
63 
64     // The network we're running on, and its type.
65     private final NetworkAgentInfo mNetwork;
66 
67     private enum State {
68         IDLE,       // start() not called. Base iface and stacked iface names are null.
69         STARTING,   // start() called. Base iface and stacked iface names are known.
70         RUNNING,    // start() called, and the stacked iface is known to be up.
71         STOPPING;   // stop() called, this Nat464Xlat is still registered as a network observer for
72                     // the stacked interface.
73     }
74 
75     private String mBaseIface;
76     private String mIface;
77     private State mState = State.IDLE;
78 
Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai)79     public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
80         mNMService = nmService;
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      */
requiresClat(NetworkAgentInfo nai)89     public static boolean requiresClat(NetworkAgentInfo nai) {
90         // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
91         final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
92         final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState());
93         // We only run clat on networks that don't have a native IPv4 address.
94         final boolean hasIPv4Address =
95                 (nai.linkProperties != null) && nai.linkProperties.hasIPv4Address();
96         return supported && connected && !hasIPv4Address;
97     }
98 
99     /**
100      * @return true if clatd has been started and has not yet stopped.
101      * A true result corresponds to internal states STARTING and RUNNING.
102      */
isStarted()103     public boolean isStarted() {
104         return mState != State.IDLE;
105     }
106 
107     /**
108      * @return true if clatd has been started but the stacked interface is not yet up.
109      */
isStarting()110     public boolean isStarting() {
111         return mState == State.STARTING;
112     }
113 
114     /**
115      * @return true if clatd has been started and the stacked interface is up.
116      */
isRunning()117     public boolean isRunning() {
118         return mState == State.RUNNING;
119     }
120 
121     /**
122      * @return true if clatd has been stopped.
123      */
isStopping()124     public boolean isStopping() {
125         return mState == State.STOPPING;
126     }
127 
128     /**
129      * Start clatd, register this Nat464Xlat as a network observer for the stacked interface,
130      * and set internal state.
131      */
enterStartingState(String baseIface)132     private void enterStartingState(String baseIface) {
133         try {
134             mNMService.registerObserver(this);
135         } catch(RemoteException e) {
136             Slog.e(TAG,
137                     "startClat: Can't register interface observer for clat on " + mNetwork.name());
138             return;
139         }
140         try {
141             mNMService.startClatd(baseIface);
142         } catch(RemoteException|IllegalStateException e) {
143             Slog.e(TAG, "Error starting clatd on " + baseIface, e);
144         }
145         mIface = CLAT_PREFIX + baseIface;
146         mBaseIface = baseIface;
147         mState = State.STARTING;
148     }
149 
150     /**
151      * Enter running state just after getting confirmation that the stacked interface is up, and
152      * turn ND offload off if on WiFi.
153      */
enterRunningState()154     private void enterRunningState() {
155         mState = State.RUNNING;
156     }
157 
158     /**
159      * Stop clatd, and turn ND offload on if it had been turned off.
160      */
enterStoppingState()161     private void enterStoppingState() {
162         try {
163             mNMService.stopClatd(mBaseIface);
164         } catch(RemoteException|IllegalStateException e) {
165             Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
166         }
167 
168         mState = State.STOPPING;
169     }
170 
171     /**
172      * Unregister as a base observer for the stacked interface, and clear internal state.
173      */
enterIdleState()174     private void enterIdleState() {
175         try {
176             mNMService.unregisterObserver(this);
177         } catch(RemoteException|IllegalStateException e) {
178             Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface, e);
179         }
180 
181         mIface = null;
182         mBaseIface = null;
183         mState = State.IDLE;
184     }
185 
186     /**
187      * Starts the clat daemon.
188      */
start()189     public void start() {
190         if (isStarted()) {
191             Slog.e(TAG, "startClat: already started");
192             return;
193         }
194 
195         if (mNetwork.linkProperties == null) {
196             Slog.e(TAG, "startClat: Can't start clat with null LinkProperties");
197             return;
198         }
199 
200         String baseIface = mNetwork.linkProperties.getInterfaceName();
201         if (baseIface == null) {
202             Slog.e(TAG, "startClat: Can't start clat on null interface");
203             return;
204         }
205         // TODO: should we only do this if mNMService.startClatd() succeeds?
206         Slog.i(TAG, "Starting clatd on " + baseIface);
207         enterStartingState(baseIface);
208     }
209 
210     /**
211      * Stops the clat daemon.
212      */
stop()213     public void stop() {
214         if (!isStarted()) {
215             return;
216         }
217         Slog.i(TAG, "Stopping clatd on " + mBaseIface);
218 
219         boolean wasStarting = isStarting();
220         enterStoppingState();
221         if (wasStarting) {
222             enterIdleState();
223         }
224     }
225 
226     /**
227      * Copies the stacked clat link in oldLp, if any, to the passed LinkProperties.
228      * This is necessary because the LinkProperties in mNetwork come from the transport layer, which
229      * has no idea that 464xlat is running on top of it.
230      */
fixupLinkProperties(LinkProperties oldLp, LinkProperties lp)231     public void fixupLinkProperties(LinkProperties oldLp, LinkProperties lp) {
232         if (!isRunning()) {
233             return;
234         }
235         if (lp == null || lp.getAllInterfaceNames().contains(mIface)) {
236             return;
237         }
238 
239         Slog.d(TAG, "clatd running, updating NAI for " + mIface);
240         for (LinkProperties stacked: oldLp.getStackedLinks()) {
241             if (Objects.equals(mIface, stacked.getInterfaceName())) {
242                 lp.addStackedLink(stacked);
243                 return;
244             }
245         }
246     }
247 
makeLinkProperties(LinkAddress clatAddress)248     private LinkProperties makeLinkProperties(LinkAddress clatAddress) {
249         LinkProperties stacked = new LinkProperties();
250         stacked.setInterfaceName(mIface);
251 
252         // Although the clat interface is a point-to-point tunnel, we don't
253         // point the route directly at the interface because some apps don't
254         // understand routes without gateways (see, e.g., http://b/9597256
255         // http://b/9597516). Instead, set the next hop of the route to the
256         // clat IPv4 address itself (for those apps, it doesn't matter what
257         // the IP of the gateway is, only that there is one).
258         RouteInfo ipv4Default = new RouteInfo(
259                 new LinkAddress(Inet4Address.ANY, 0),
260                 clatAddress.getAddress(), mIface);
261         stacked.addRoute(ipv4Default);
262         stacked.addLinkAddress(clatAddress);
263         return stacked;
264     }
265 
getLinkAddress(String iface)266     private LinkAddress getLinkAddress(String iface) {
267         try {
268             InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
269             return config.getLinkAddress();
270         } catch(RemoteException|IllegalStateException e) {
271             Slog.e(TAG, "Error getting link properties: " + e);
272             return null;
273         }
274     }
275 
276     /**
277      * Adds stacked link on base link and transitions to RUNNING state.
278      */
handleInterfaceLinkStateChanged(String iface, boolean up)279     private void handleInterfaceLinkStateChanged(String iface, boolean up) {
280         if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
281             return;
282         }
283 
284         LinkAddress clatAddress = getLinkAddress(iface);
285         if (clatAddress == null) {
286             Slog.e(TAG, "clatAddress was null for stacked iface " + iface);
287             return;
288         }
289 
290         Slog.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s",
291                 mIface, mIface, mBaseIface));
292         enterRunningState();
293         LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
294         lp.addStackedLink(makeLinkProperties(clatAddress));
295         mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
296     }
297 
298     /**
299      * Removes stacked link on base link and transitions to IDLE state.
300      */
handleInterfaceRemoved(String iface)301     private void handleInterfaceRemoved(String iface) {
302         if (!Objects.equals(mIface, iface)) {
303             return;
304         }
305         if (!isRunning() && !isStopping()) {
306             return;
307         }
308 
309         Slog.i(TAG, "interface " + iface + " removed");
310         if (!isStopping()) {
311             // Ensure clatd is stopped if stop() has not been called: this likely means that clatd
312             // has crashed.
313             enterStoppingState();
314         }
315         enterIdleState();
316         LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
317         lp.removeStackedLink(iface);
318         mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
319     }
320 
321     @Override
interfaceLinkStateChanged(String iface, boolean up)322     public void interfaceLinkStateChanged(String iface, boolean up) {
323         mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); });
324     }
325 
326     @Override
interfaceRemoved(String iface)327     public void interfaceRemoved(String iface) {
328         mNetwork.handler().post(() -> { handleInterfaceRemoved(iface); });
329     }
330 
331     @Override
toString()332     public String toString() {
333         return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
334     }
335 }
336