Nat464Xlat.java revision 3c18216c26c7bd0580ca4bfb7daa58992037f2c9
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
17package com.android.server.connectivity;
18
19import android.net.InterfaceConfiguration;
20import android.net.ConnectivityManager;
21import android.net.LinkAddress;
22import android.net.LinkProperties;
23import android.net.NetworkInfo;
24import android.net.RouteInfo;
25import android.os.INetworkManagementService;
26import android.os.RemoteException;
27import android.util.Slog;
28
29import com.android.internal.util.ArrayUtils;
30import com.android.server.net.BaseNetworkObserver;
31
32import java.net.Inet4Address;
33import 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 */
42public 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
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     */
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     */
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     */
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     */
117    public boolean isRunning() {
118        return mState == State.RUNNING;
119    }
120
121    /**
122     * @return true if clatd has been stopped.
123     */
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     */
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     */
154    private void enterRunningState() {
155        maybeSetIpv6NdOffload(mBaseIface, false);
156        mState = State.RUNNING;
157    }
158
159    /**
160     * Stop clatd, and turn ND offload on if it had been turned off.
161     */
162    private void enterStoppingState() {
163        if (isRunning()) {
164            maybeSetIpv6NdOffload(mBaseIface, true);
165        }
166
167        try {
168            mNMService.stopClatd(mBaseIface);
169        } catch(RemoteException|IllegalStateException e) {
170            Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
171        }
172
173        mState = State.STOPPING;
174    }
175
176    /**
177     * Unregister as a base observer for the stacked interface, and clear internal state.
178     */
179    private void enterIdleState() {
180        try {
181            mNMService.unregisterObserver(this);
182        } catch(RemoteException|IllegalStateException e) {
183            Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface, e);
184        }
185
186        mIface = null;
187        mBaseIface = null;
188        mState = State.IDLE;
189    }
190
191    /**
192     * Starts the clat daemon.
193     */
194    public void start() {
195        if (isStarted()) {
196            Slog.e(TAG, "startClat: already started");
197            return;
198        }
199
200        if (mNetwork.linkProperties == null) {
201            Slog.e(TAG, "startClat: Can't start clat with null LinkProperties");
202            return;
203        }
204
205        String baseIface = mNetwork.linkProperties.getInterfaceName();
206        if (baseIface == null) {
207            Slog.e(TAG, "startClat: Can't start clat on null interface");
208            return;
209        }
210        // TODO: should we only do this if mNMService.startClatd() succeeds?
211        Slog.i(TAG, "Starting clatd on " + baseIface);
212        enterStartingState(baseIface);
213    }
214
215    /**
216     * Stops the clat daemon.
217     */
218    public void stop() {
219        if (!isStarted()) {
220            return;
221        }
222        Slog.i(TAG, "Stopping clatd on " + mBaseIface);
223
224        boolean wasStarting = isStarting();
225        enterStoppingState();
226        if (wasStarting) {
227            enterIdleState();
228        }
229    }
230
231    /**
232     * Copies the stacked clat link in oldLp, if any, to the LinkProperties in mNetwork.
233     * This is necessary because the LinkProperties in mNetwork come from the transport layer, which
234     * has no idea that 464xlat is running on top of it.
235     */
236    public void fixupLinkProperties(LinkProperties oldLp) {
237        if (!isRunning()) {
238            return;
239        }
240        LinkProperties lp = mNetwork.linkProperties;
241        if (lp == null || lp.getAllInterfaceNames().contains(mIface)) {
242            return;
243        }
244
245        Slog.d(TAG, "clatd running, updating NAI for " + mIface);
246        for (LinkProperties stacked: oldLp.getStackedLinks()) {
247            if (Objects.equals(mIface, stacked.getInterfaceName())) {
248                lp.addStackedLink(stacked);
249                return;
250            }
251        }
252    }
253
254    private LinkProperties makeLinkProperties(LinkAddress clatAddress) {
255        LinkProperties stacked = new LinkProperties();
256        stacked.setInterfaceName(mIface);
257
258        // Although the clat interface is a point-to-point tunnel, we don't
259        // point the route directly at the interface because some apps don't
260        // understand routes without gateways (see, e.g., http://b/9597256
261        // http://b/9597516). Instead, set the next hop of the route to the
262        // clat IPv4 address itself (for those apps, it doesn't matter what
263        // the IP of the gateway is, only that there is one).
264        RouteInfo ipv4Default = new RouteInfo(
265                new LinkAddress(Inet4Address.ANY, 0),
266                clatAddress.getAddress(), mIface);
267        stacked.addRoute(ipv4Default);
268        stacked.addLinkAddress(clatAddress);
269        return stacked;
270    }
271
272    private LinkAddress getLinkAddress(String iface) {
273        try {
274            InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
275            return config.getLinkAddress();
276        } catch(RemoteException|IllegalStateException e) {
277            Slog.e(TAG, "Error getting link properties: " + e);
278            return null;
279        }
280    }
281
282    private void maybeSetIpv6NdOffload(String iface, boolean on) {
283        // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
284        if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
285            return;
286        }
287        try {
288            Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
289            mNMService.setInterfaceIpv6NdOffload(iface, on);
290        } catch(RemoteException|IllegalStateException e) {
291            Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
292        }
293    }
294
295    /**
296     * Adds stacked link on base link and transitions to RUNNING state.
297     */
298    private void handleInterfaceLinkStateChanged(String iface, boolean up) {
299        if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
300            return;
301        }
302
303        LinkAddress clatAddress = getLinkAddress(iface);
304        if (clatAddress == null) {
305            Slog.e(TAG, "clatAddress was null for stacked iface " + iface);
306            return;
307        }
308
309        Slog.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s",
310                mIface, mIface, mBaseIface));
311        enterRunningState();
312        LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
313        lp.addStackedLink(makeLinkProperties(clatAddress));
314        mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
315    }
316
317    /**
318     * Removes stacked link on base link and transitions to IDLE state.
319     */
320    private void handleInterfaceRemoved(String iface) {
321        if (!Objects.equals(mIface, iface)) {
322            return;
323        }
324        if (!isRunning() && !isStopping()) {
325            return;
326        }
327
328        Slog.i(TAG, "interface " + iface + " removed");
329        if (!isStopping()) {
330            // Ensure clatd is stopped if stop() has not been called: this likely means that clatd
331            // has crashed.
332            enterStoppingState();
333        }
334        enterIdleState();
335        LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
336        lp.removeStackedLink(iface);
337        mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
338    }
339
340    @Override
341    public void interfaceLinkStateChanged(String iface, boolean up) {
342        mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); });
343    }
344
345    @Override
346    public void interfaceRemoved(String iface) {
347        mNetwork.handler().post(() -> { handleInterfaceRemoved(iface); });
348    }
349
350    @Override
351    public String toString() {
352        return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
353    }
354}
355