Nat464Xlat.java revision a56daad1471325913a82d93456c9f3fc081b6f7b
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        mState = State.RUNNING;
156    }
157
158    /**
159     * Stop clatd, and turn ND offload on if it had been turned off.
160     */
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     */
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     */
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     */
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     */
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
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
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     */
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     */
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
322    public void interfaceLinkStateChanged(String iface, boolean up) {
323        mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); });
324    }
325
326    @Override
327    public void interfaceRemoved(String iface) {
328        mNetwork.handler().post(() -> { handleInterfaceRemoved(iface); });
329    }
330
331    @Override
332    public String toString() {
333        return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
334    }
335}
336