• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.NetworkCapabilities.TRANSPORT_CELLULAR;
20 
21 import static com.android.net.module.util.CollectionUtils.contains;
22 
23 import android.annotation.NonNull;
24 import android.net.ConnectivityManager;
25 import android.net.IDnsResolver;
26 import android.net.INetd;
27 import android.net.InetAddresses;
28 import android.net.InterfaceConfigurationParcel;
29 import android.net.IpPrefix;
30 import android.net.LinkAddress;
31 import android.net.LinkProperties;
32 import android.net.NetworkInfo;
33 import android.net.RouteInfo;
34 import android.os.RemoteException;
35 import android.os.ServiceSpecificException;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.util.IndentingPrintWriter;
40 import com.android.modules.utils.build.SdkLevel;
41 import com.android.net.module.util.NetworkStackConstants;
42 import com.android.server.ConnectivityService;
43 
44 import java.io.IOException;
45 import java.net.Inet6Address;
46 import java.util.Objects;
47 
48 /**
49  * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated
50  * from a consistent and unique thread context. It is the responsibility of ConnectivityService to
51  * call into this class from its own Handler thread.
52  *
53  * @hide
54  */
55 public class Nat464Xlat {
56     private static final String TAG = Nat464Xlat.class.getSimpleName();
57 
58     // This must match the interface prefix in clatd.c.
59     private static final String CLAT_PREFIX = "v4-";
60 
61     // The network types on which we will start clatd,
62     // allowing clat only on networks for which we can support IPv6-only.
63     private static final int[] NETWORK_TYPES = {
64         ConnectivityManager.TYPE_MOBILE,
65         ConnectivityManager.TYPE_WIFI,
66         ConnectivityManager.TYPE_ETHERNET,
67     };
68 
69     // The network states in which running clatd is supported.
70     private static final NetworkInfo.State[] NETWORK_STATES = {
71         NetworkInfo.State.CONNECTED,
72         NetworkInfo.State.SUSPENDED,
73     };
74 
75     private final IDnsResolver mDnsResolver;
76     private final INetd mNetd;
77 
78     // The network we're running on, and its type.
79     private final NetworkAgentInfo mNetwork;
80 
81     private enum State {
82         IDLE,         // start() not called. Base iface and stacked iface names are null.
83         DISCOVERING,  // same as IDLE, except prefix discovery in progress.
84         STARTING,     // start() called. Base iface and stacked iface names are known.
85         RUNNING,      // start() called, and the stacked iface is known to be up.
86     }
87 
88     /**
89      * NAT64 prefix currently in use. Only valid in STARTING or RUNNING states.
90      * Used, among other things, to avoid updates when switching from a prefix learned from one
91      * source (e.g., RA) to the same prefix learned from another source (e.g., RA).
92      */
93     private IpPrefix mNat64PrefixInUse;
94     /** NAT64 prefix (if any) discovered from DNS via RFC 7050. */
95     private IpPrefix mNat64PrefixFromDns;
96     /** NAT64 prefix (if any) learned from the network via RA. */
97     private IpPrefix mNat64PrefixFromRa;
98     private String mBaseIface;
99     private String mIface;
100     private Inet6Address mIPv6Address;
101     private State mState = State.IDLE;
102     private ClatCoordinator mClatCoordinator;
103 
104     private boolean mEnableClatOnCellular;
105     private boolean mPrefixDiscoveryRunning;
106 
Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver, ConnectivityService.Dependencies deps)107     public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver,
108             ConnectivityService.Dependencies deps) {
109         mDnsResolver = dnsResolver;
110         mNetd = netd;
111         mNetwork = nai;
112         mEnableClatOnCellular = deps.getCellular464XlatEnabled();
113         mClatCoordinator = deps.getClatCoordinator(mNetd);
114     }
115 
116     /**
117      * Whether to attempt 464xlat on this network. This is true for an IPv6-only network that is
118      * currently connected and where the NetworkAgent has not disabled 464xlat. It is the signal to
119      * enable NAT64 prefix discovery.
120      *
121      * @param nai the NetworkAgentInfo corresponding to the network.
122      * @return true if the network requires clat, false otherwise.
123      */
124     @VisibleForTesting
requiresClat(NetworkAgentInfo nai)125     protected boolean requiresClat(NetworkAgentInfo nai) {
126         // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
127         final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType());
128         final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState());
129 
130         // Only run clat on networks that have a global IPv6 address and don't have a native IPv4
131         // address.
132         LinkProperties lp = nai.linkProperties;
133         final boolean isIpv6OnlyNetwork = (lp != null) && lp.hasGlobalIpv6Address()
134                 && !lp.hasIpv4Address();
135 
136         // If the network tells us it doesn't use clat, respect that.
137         final boolean skip464xlat = (nai.netAgentConfig() != null)
138                 && nai.netAgentConfig().skip464xlat;
139 
140         return supported && connected && isIpv6OnlyNetwork && !skip464xlat && !nai.destroyed
141                 && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
142                 ? isCellular464XlatEnabled() : true);
143     }
144 
145     /**
146      * Whether the clat demon should be started on this network now. This is true if requiresClat is
147      * true and a NAT64 prefix has been discovered.
148      *
149      * @param nai the NetworkAgentInfo corresponding to the network.
150      * @return true if the network should start clat, false otherwise.
151      */
152     @VisibleForTesting
shouldStartClat(NetworkAgentInfo nai)153     protected boolean shouldStartClat(NetworkAgentInfo nai) {
154         LinkProperties lp = nai.linkProperties;
155         return requiresClat(nai) && lp != null && lp.getNat64Prefix() != null;
156     }
157 
158     /**
159      * @return true if clatd has been started and has not yet stopped.
160      * A true result corresponds to internal states STARTING and RUNNING.
161      */
isStarted()162     public boolean isStarted() {
163         return (mState == State.STARTING || mState == State.RUNNING);
164     }
165 
166     /**
167      * @return true if clatd has been started but the stacked interface is not yet up.
168      */
isStarting()169     public boolean isStarting() {
170         return mState == State.STARTING;
171     }
172 
173     /**
174      * @return true if clatd has been started and the stacked interface is up.
175      */
isRunning()176     public boolean isRunning() {
177         return mState == State.RUNNING;
178     }
179 
180     /**
181      * Start clatd, register this Nat464Xlat as a network observer for the stacked interface,
182      * and set internal state.
183      */
enterStartingState(String baseIface)184     private void enterStartingState(String baseIface) {
185         mNat64PrefixInUse = selectNat64Prefix();
186         String addrStr = null;
187         if (SdkLevel.isAtLeastT()) {
188             try {
189                 addrStr = mClatCoordinator.clatStart(baseIface, getNetId(), mNat64PrefixInUse);
190             } catch (IOException e) {
191                 Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
192             }
193         } else {
194             try {
195                 addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
196             } catch (RemoteException | ServiceSpecificException e) {
197                 Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
198             }
199         }
200         mIface = CLAT_PREFIX + baseIface;
201         mBaseIface = baseIface;
202         mState = State.STARTING;
203         try {
204             mIPv6Address = (Inet6Address) InetAddresses.parseNumericAddress(addrStr);
205         } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
206             Log.e(TAG, "Invalid IPv6 address " + addrStr);
207         }
208         if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) {
209             stopPrefixDiscovery();
210         }
211         if (!mPrefixDiscoveryRunning) {
212             setPrefix64(mNat64PrefixInUse);
213         }
214     }
215 
216     /**
217      * Enter running state just after getting confirmation that the stacked interface is up, and
218      * turn ND offload off if on WiFi.
219      */
enterRunningState()220     private void enterRunningState() {
221         mState = State.RUNNING;
222     }
223 
224     /**
225      * Unregister as a base observer for the stacked interface, and clear internal state.
226      */
leaveStartedState()227     private void leaveStartedState() {
228         mNat64PrefixInUse = null;
229         mIface = null;
230         mBaseIface = null;
231 
232         if (!mPrefixDiscoveryRunning) {
233             setPrefix64(null);
234         }
235 
236         if (isPrefixDiscoveryNeeded()) {
237             if (!mPrefixDiscoveryRunning) {
238                 startPrefixDiscovery();
239             }
240             mState = State.DISCOVERING;
241         } else {
242             stopPrefixDiscovery();
243             mState = State.IDLE;
244         }
245     }
246 
247     @VisibleForTesting
start()248     protected void start() {
249         if (isStarted()) {
250             Log.e(TAG, "startClat: already started");
251             return;
252         }
253 
254         String baseIface = mNetwork.linkProperties.getInterfaceName();
255         if (baseIface == null) {
256             Log.e(TAG, "startClat: Can't start clat on null interface");
257             return;
258         }
259         // TODO: should we only do this if mNetd.clatdStart() succeeds?
260         Log.i(TAG, "Starting clatd on " + baseIface);
261         enterStartingState(baseIface);
262     }
263 
264     @VisibleForTesting
stop()265     protected void stop() {
266         if (!isStarted()) {
267             Log.e(TAG, "stopClat: already stopped");
268             return;
269         }
270 
271         Log.i(TAG, "Stopping clatd on " + mBaseIface);
272         if (SdkLevel.isAtLeastT()) {
273             try {
274                 mClatCoordinator.clatStop();
275             } catch (IOException e) {
276                 Log.e(TAG, "Error stopping clatd on " + mBaseIface + ": " + e);
277             }
278         } else {
279             try {
280                 mNetd.clatdStop(mBaseIface);
281             } catch (RemoteException | ServiceSpecificException e) {
282                 Log.e(TAG, "Error stopping clatd on " + mBaseIface + ": " + e);
283             }
284         }
285 
286         String iface = mIface;
287         boolean wasRunning = isRunning();
288 
289         // Change state before updating LinkProperties. handleUpdateLinkProperties ends up calling
290         // fixupLinkProperties, and if at that time the state is still RUNNING, fixupLinkProperties
291         // would wrongly inform ConnectivityService that there is still a stacked interface.
292         leaveStartedState();
293 
294         if (wasRunning) {
295             LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
296             lp.removeStackedLink(iface);
297             mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
298         }
299     }
300 
startPrefixDiscovery()301     private void startPrefixDiscovery() {
302         try {
303             mDnsResolver.startPrefix64Discovery(getNetId());
304         } catch (RemoteException | ServiceSpecificException e) {
305             Log.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e);
306         }
307         mPrefixDiscoveryRunning = true;
308     }
309 
stopPrefixDiscovery()310     private void stopPrefixDiscovery() {
311         try {
312             mDnsResolver.stopPrefix64Discovery(getNetId());
313         } catch (RemoteException | ServiceSpecificException e) {
314             Log.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e);
315         }
316         mPrefixDiscoveryRunning = false;
317     }
318 
isPrefixDiscoveryNeeded()319     private boolean isPrefixDiscoveryNeeded() {
320         // If there is no NAT64 prefix in the RA, prefix discovery is always needed. It cannot be
321         // stopped after it succeeds, because stopping it will cause netd to report that the prefix
322         // has been removed, and that will cause us to stop clatd.
323         return requiresClat(mNetwork) && mNat64PrefixFromRa == null;
324     }
325 
setPrefix64(IpPrefix prefix)326     private void setPrefix64(IpPrefix prefix) {
327         final String prefixString = (prefix != null) ? prefix.toString() : "";
328         try {
329             mDnsResolver.setPrefix64(getNetId(), prefixString);
330         } catch (RemoteException | ServiceSpecificException e) {
331             Log.e(TAG, "Error setting NAT64 prefix on netId " + getNetId() + " to "
332                     + prefix + ": " + e);
333         }
334     }
335 
maybeHandleNat64PrefixChange()336     private void maybeHandleNat64PrefixChange() {
337         final IpPrefix newPrefix = selectNat64Prefix();
338         if (!Objects.equals(mNat64PrefixInUse, newPrefix)) {
339             Log.d(TAG, "NAT64 prefix changed from " + mNat64PrefixInUse + " to "
340                     + newPrefix);
341             stop();
342             // It's safe to call update here, even though this method is called from update, because
343             // stop() is guaranteed to have moved out of STARTING and RUNNING, which are the only
344             // states in which this method can be called.
345             update();
346         }
347     }
348 
349     /**
350      * Starts/stops NAT64 prefix discovery and clatd as necessary.
351      */
update()352     public void update() {
353         // TODO: turn this class into a proper StateMachine. http://b/126113090
354         switch (mState) {
355             case IDLE:
356                 if (isPrefixDiscoveryNeeded()) {
357                     startPrefixDiscovery();  // Enters DISCOVERING state.
358                     mState = State.DISCOVERING;
359                 } else if (requiresClat(mNetwork)) {
360                     start();  // Enters STARTING state.
361                 }
362                 break;
363 
364             case DISCOVERING:
365                 if (shouldStartClat(mNetwork)) {
366                     // NAT64 prefix detected. Start clatd.
367                     start();  // Enters STARTING state.
368                     return;
369                 }
370                 if (!requiresClat(mNetwork)) {
371                     // IPv4 address added. Go back to IDLE state.
372                     stopPrefixDiscovery();
373                     mState = State.IDLE;
374                     return;
375                 }
376                 break;
377 
378             case STARTING:
379             case RUNNING:
380                 // NAT64 prefix removed, or IPv4 address added.
381                 // Stop clatd and go back into DISCOVERING or idle.
382                 if (!shouldStartClat(mNetwork)) {
383                     stop();
384                     break;
385                 }
386                 // Only necessary while clat is actually started.
387                 maybeHandleNat64PrefixChange();
388                 break;
389         }
390     }
391 
392     /**
393      * Picks a NAT64 prefix to use. Always prefers the prefix from the RA if one is received from
394      * both RA and DNS, because the prefix in the RA has better security and updatability, and will
395      * almost always be received first anyway.
396      *
397      * Any network that supports legacy hosts will support discovering the DNS64 prefix via DNS as
398      * well. If the prefix from the RA is withdrawn, fall back to that for reliability purposes.
399      */
selectNat64Prefix()400     private IpPrefix selectNat64Prefix() {
401         return mNat64PrefixFromRa != null ? mNat64PrefixFromRa : mNat64PrefixFromDns;
402     }
403 
setNat64PrefixFromRa(IpPrefix prefix)404     public void setNat64PrefixFromRa(IpPrefix prefix) {
405         mNat64PrefixFromRa = prefix;
406     }
407 
setNat64PrefixFromDns(IpPrefix prefix)408     public void setNat64PrefixFromDns(IpPrefix prefix) {
409         mNat64PrefixFromDns = prefix;
410     }
411 
412     /**
413      * Copies the stacked clat link in oldLp, if any, to the passed LinkProperties.
414      * This is necessary because the LinkProperties in mNetwork come from the transport layer, which
415      * has no idea that 464xlat is running on top of it.
416      */
fixupLinkProperties(@onNull LinkProperties oldLp, @NonNull LinkProperties lp)417     public void fixupLinkProperties(@NonNull LinkProperties oldLp, @NonNull LinkProperties lp) {
418         // This must be done even if clatd is not running, because otherwise shouldStartClat would
419         // never return true.
420         lp.setNat64Prefix(selectNat64Prefix());
421 
422         if (!isRunning()) {
423             return;
424         }
425         if (lp.getAllInterfaceNames().contains(mIface)) {
426             return;
427         }
428 
429         Log.d(TAG, "clatd running, updating NAI for " + mIface);
430         for (LinkProperties stacked: oldLp.getStackedLinks()) {
431             if (Objects.equals(mIface, stacked.getInterfaceName())) {
432                 lp.addStackedLink(stacked);
433                 return;
434             }
435         }
436     }
437 
makeLinkProperties(LinkAddress clatAddress)438     private LinkProperties makeLinkProperties(LinkAddress clatAddress) {
439         LinkProperties stacked = new LinkProperties();
440         stacked.setInterfaceName(mIface);
441 
442         // Although the clat interface is a point-to-point tunnel, we don't
443         // point the route directly at the interface because some apps don't
444         // understand routes without gateways (see, e.g., http://b/9597256
445         // http://b/9597516). Instead, set the next hop of the route to the
446         // clat IPv4 address itself (for those apps, it doesn't matter what
447         // the IP of the gateway is, only that there is one).
448         RouteInfo ipv4Default = new RouteInfo(
449                 new LinkAddress(NetworkStackConstants.IPV4_ADDR_ANY, 0),
450                 clatAddress.getAddress(), mIface);
451         stacked.addRoute(ipv4Default);
452         stacked.addLinkAddress(clatAddress);
453         return stacked;
454     }
455 
getLinkAddress(String iface)456     private LinkAddress getLinkAddress(String iface) {
457         try {
458             final InterfaceConfigurationParcel config = mNetd.interfaceGetCfg(iface);
459             return new LinkAddress(
460                     InetAddresses.parseNumericAddress(config.ipv4Addr), config.prefixLength);
461         } catch (IllegalArgumentException | RemoteException | ServiceSpecificException e) {
462             Log.e(TAG, "Error getting link properties: " + e);
463             return null;
464         }
465     }
466 
467     /**
468      * Adds stacked link on base link and transitions to RUNNING state.
469      */
handleInterfaceLinkStateChanged(String iface, boolean up)470     private void handleInterfaceLinkStateChanged(String iface, boolean up) {
471         // TODO: if we call start(), then stop(), then start() again, and the
472         // interfaceLinkStateChanged notification for the first start is delayed past the first
473         // stop, then the code becomes out of sync with system state and will behave incorrectly.
474         //
475         // This is not trivial to fix because:
476         // 1. It is not guaranteed that start() will eventually result in the interface coming up,
477         //    because there could be an error starting clat (e.g., if the interface goes down before
478         //    the packet socket can be bound).
479         // 2. If start is called multiple times, there is nothing in the interfaceLinkStateChanged
480         //    notification that says which start() call the interface was created by.
481         //
482         // Once this code is converted to StateMachine, it will be possible to use deferMessage to
483         // ensure it stays in STARTING state until the interfaceLinkStateChanged notification fires,
484         // and possibly use a timeout (or provide some guarantees at the lower layer) to address #1.
485         if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
486             return;
487         }
488 
489         LinkAddress clatAddress = getLinkAddress(iface);
490         if (clatAddress == null) {
491             Log.e(TAG, "clatAddress was null for stacked iface " + iface);
492             return;
493         }
494 
495         Log.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s",
496                 mIface, mIface, mBaseIface));
497         enterRunningState();
498         LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
499         lp.addStackedLink(makeLinkProperties(clatAddress));
500         mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
501     }
502 
503     /**
504      * Removes stacked link on base link and transitions to IDLE state.
505      */
handleInterfaceRemoved(String iface)506     private void handleInterfaceRemoved(String iface) {
507         if (!Objects.equals(mIface, iface)) {
508             return;
509         }
510         if (!isRunning()) {
511             return;
512         }
513 
514         Log.i(TAG, "interface " + iface + " removed");
515         // If we're running, and the interface was removed, then we didn't call stop(), and it's
516         // likely that clatd crashed. Ensure we call stop() so we can start clatd again. Calling
517         // stop() will also update LinkProperties, and if clatd crashed, the LinkProperties update
518         // will cause ConnectivityService to call start() again.
519         stop();
520     }
521 
interfaceLinkStateChanged(String iface, boolean up)522     public void interfaceLinkStateChanged(String iface, boolean up) {
523         mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); });
524     }
525 
interfaceRemoved(String iface)526     public void interfaceRemoved(String iface) {
527         mNetwork.handler().post(() -> handleInterfaceRemoved(iface));
528     }
529 
530     /**
531      * Dump the NAT64 xlat information.
532      *
533      * @param pw print writer.
534      */
dump(IndentingPrintWriter pw)535     public void dump(IndentingPrintWriter pw) {
536         if (SdkLevel.isAtLeastT()) {
537             if (isStarted()) {
538                 pw.println("ClatCoordinator:");
539                 pw.increaseIndent();
540                 mClatCoordinator.dump(pw);
541                 pw.decreaseIndent();
542             } else {
543                 pw.println("<not start>");
544             }
545         }
546     }
547 
548     @Override
toString()549     public String toString() {
550         return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
551     }
552 
553     @VisibleForTesting
getNetId()554     protected int getNetId() {
555         return mNetwork.network.getNetId();
556     }
557 
558     @VisibleForTesting
isCellular464XlatEnabled()559     protected boolean isCellular464XlatEnabled() {
560         return mEnableClatOnCellular;
561     }
562 }
563