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.ConnectivityManager; 20 import android.net.IDnsResolver; 21 import android.net.INetd; 22 import android.net.InetAddresses; 23 import android.net.InterfaceConfiguration; 24 import android.net.IpPrefix; 25 import android.net.LinkAddress; 26 import android.net.LinkProperties; 27 import android.net.NetworkInfo; 28 import android.net.RouteInfo; 29 import android.os.INetworkManagementService; 30 import android.os.RemoteException; 31 import android.os.ServiceSpecificException; 32 import android.util.Slog; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.util.ArrayUtils; 36 import com.android.server.net.BaseNetworkObserver; 37 38 import java.net.Inet4Address; 39 import java.net.Inet6Address; 40 import java.util.Objects; 41 42 /** 43 * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated 44 * from a consistent and unique thread context. It is the responsibility of ConnectivityService to 45 * call into this class from its own Handler thread. 46 * 47 * @hide 48 */ 49 public class Nat464Xlat extends BaseNetworkObserver { 50 private static final String TAG = Nat464Xlat.class.getSimpleName(); 51 52 // This must match the interface prefix in clatd.c. 53 private static final String CLAT_PREFIX = "v4-"; 54 55 // The network types on which we will start clatd, 56 // allowing clat only on networks for which we can support IPv6-only. 57 private static final int[] NETWORK_TYPES = { 58 ConnectivityManager.TYPE_MOBILE, 59 ConnectivityManager.TYPE_WIFI, 60 ConnectivityManager.TYPE_ETHERNET, 61 }; 62 63 // The network states in which running clatd is supported. 64 private static final NetworkInfo.State[] NETWORK_STATES = { 65 NetworkInfo.State.CONNECTED, 66 NetworkInfo.State.SUSPENDED, 67 }; 68 69 private final IDnsResolver mDnsResolver; 70 private final INetd mNetd; 71 private final INetworkManagementService mNMService; 72 73 // The network we're running on, and its type. 74 private final NetworkAgentInfo mNetwork; 75 76 private enum State { 77 IDLE, // start() not called. Base iface and stacked iface names are null. 78 DISCOVERING, // same as IDLE, except prefix discovery in progress. 79 STARTING, // start() called. Base iface and stacked iface names are known. 80 RUNNING, // start() called, and the stacked iface is known to be up. 81 } 82 83 private IpPrefix mNat64Prefix; 84 private String mBaseIface; 85 private String mIface; 86 private Inet6Address mIPv6Address; 87 private State mState = State.IDLE; 88 Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nmService)89 public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver, 90 INetworkManagementService nmService) { 91 mDnsResolver = dnsResolver; 92 mNetd = netd; 93 mNMService = nmService; 94 mNetwork = nai; 95 } 96 97 /** 98 * Whether to attempt 464xlat on this network. This is true for an IPv6-only network that is 99 * currently connected and where the NetworkAgent has not disabled 464xlat. It is the signal to 100 * enable NAT64 prefix discovery. 101 * 102 * @param network the NetworkAgentInfo corresponding to the network. 103 * @return true if the network requires clat, false otherwise. 104 */ 105 @VisibleForTesting requiresClat(NetworkAgentInfo nai)106 protected static boolean requiresClat(NetworkAgentInfo nai) { 107 // TODO: migrate to NetworkCapabilities.TRANSPORT_*. 108 final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType()); 109 final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState()); 110 111 // Only run clat on networks that have a global IPv6 address and don't have a native IPv4 112 // address. 113 LinkProperties lp = nai.linkProperties; 114 final boolean isIpv6OnlyNetwork = (lp != null) && lp.hasGlobalIpv6Address() 115 && !lp.hasIpv4Address(); 116 117 // If the network tells us it doesn't use clat, respect that. 118 final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat; 119 120 return supported && connected && isIpv6OnlyNetwork && !skip464xlat; 121 } 122 123 /** 124 * Whether the clat demon should be started on this network now. This is true if requiresClat is 125 * true and a NAT64 prefix has been discovered. 126 * 127 * @param nai the NetworkAgentInfo corresponding to the network. 128 * @return true if the network should start clat, false otherwise. 129 */ 130 @VisibleForTesting shouldStartClat(NetworkAgentInfo nai)131 protected static boolean shouldStartClat(NetworkAgentInfo nai) { 132 LinkProperties lp = nai.linkProperties; 133 return requiresClat(nai) && lp != null && lp.getNat64Prefix() != null; 134 } 135 136 /** 137 * @return true if we have started prefix discovery and not yet stopped it (regardless of 138 * whether it is still running or has succeeded). 139 * A true result corresponds to internal states DISCOVERING, STARTING and RUNNING. 140 */ isPrefixDiscoveryStarted()141 public boolean isPrefixDiscoveryStarted() { 142 return mState == State.DISCOVERING || isStarted(); 143 } 144 145 /** 146 * @return true if clatd has been started and has not yet stopped. 147 * A true result corresponds to internal states STARTING and RUNNING. 148 */ isStarted()149 public boolean isStarted() { 150 return (mState == State.STARTING || mState == State.RUNNING); 151 } 152 153 /** 154 * @return true if clatd has been started but the stacked interface is not yet up. 155 */ isStarting()156 public boolean isStarting() { 157 return mState == State.STARTING; 158 } 159 160 /** 161 * @return true if clatd has been started and the stacked interface is up. 162 */ isRunning()163 public boolean isRunning() { 164 return mState == State.RUNNING; 165 } 166 167 /** 168 * Start clatd, register this Nat464Xlat as a network observer for the stacked interface, 169 * and set internal state. 170 */ enterStartingState(String baseIface)171 private void enterStartingState(String baseIface) { 172 try { 173 mNMService.registerObserver(this); 174 } catch (RemoteException e) { 175 Slog.e(TAG, "Can't register interface observer for clat on " + mNetwork.name()); 176 return; 177 } 178 179 String addrStr = null; 180 try { 181 addrStr = mNetd.clatdStart(baseIface, mNat64Prefix.toString()); 182 } catch (RemoteException | ServiceSpecificException e) { 183 Slog.e(TAG, "Error starting clatd on " + baseIface + ": " + e); 184 } 185 mIface = CLAT_PREFIX + baseIface; 186 mBaseIface = baseIface; 187 mState = State.STARTING; 188 try { 189 mIPv6Address = (Inet6Address) InetAddresses.parseNumericAddress(addrStr); 190 } catch (ClassCastException | IllegalArgumentException | NullPointerException e) { 191 Slog.e(TAG, "Invalid IPv6 address " + addrStr); 192 } 193 } 194 195 /** 196 * Enter running state just after getting confirmation that the stacked interface is up, and 197 * turn ND offload off if on WiFi. 198 */ enterRunningState()199 private void enterRunningState() { 200 mState = State.RUNNING; 201 } 202 203 /** 204 * Unregister as a base observer for the stacked interface, and clear internal state. 205 */ leaveStartedState()206 private void leaveStartedState() { 207 try { 208 mNMService.unregisterObserver(this); 209 } catch (RemoteException | IllegalStateException e) { 210 Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface + ": " + e); 211 } 212 mIface = null; 213 mBaseIface = null; 214 mState = State.IDLE; 215 if (requiresClat(mNetwork)) { 216 mState = State.DISCOVERING; 217 } else { 218 stopPrefixDiscovery(); 219 mState = State.IDLE; 220 } 221 } 222 223 @VisibleForTesting start()224 protected void start() { 225 if (isStarted()) { 226 Slog.e(TAG, "startClat: already started"); 227 return; 228 } 229 230 if (mNetwork.linkProperties == null) { 231 Slog.e(TAG, "startClat: Can't start clat with null LinkProperties"); 232 return; 233 } 234 235 String baseIface = mNetwork.linkProperties.getInterfaceName(); 236 if (baseIface == null) { 237 Slog.e(TAG, "startClat: Can't start clat on null interface"); 238 return; 239 } 240 // TODO: should we only do this if mNetd.clatdStart() succeeds? 241 Slog.i(TAG, "Starting clatd on " + baseIface); 242 enterStartingState(baseIface); 243 } 244 245 @VisibleForTesting stop()246 protected void stop() { 247 if (!isStarted()) { 248 Slog.e(TAG, "stopClat: already stopped"); 249 return; 250 } 251 252 Slog.i(TAG, "Stopping clatd on " + mBaseIface); 253 try { 254 mNetd.clatdStop(mBaseIface); 255 } catch (RemoteException | ServiceSpecificException e) { 256 Slog.e(TAG, "Error stopping clatd on " + mBaseIface + ": " + e); 257 } 258 259 String iface = mIface; 260 boolean wasRunning = isRunning(); 261 262 // Change state before updating LinkProperties. handleUpdateLinkProperties ends up calling 263 // fixupLinkProperties, and if at that time the state is still RUNNING, fixupLinkProperties 264 // would wrongly inform ConnectivityService that there is still a stacked interface. 265 leaveStartedState(); 266 267 if (wasRunning) { 268 LinkProperties lp = new LinkProperties(mNetwork.linkProperties); 269 lp.removeStackedLink(iface); 270 mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp); 271 } 272 } 273 startPrefixDiscovery()274 private void startPrefixDiscovery() { 275 try { 276 mDnsResolver.startPrefix64Discovery(getNetId()); 277 mState = State.DISCOVERING; 278 } catch (RemoteException | ServiceSpecificException e) { 279 Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e); 280 } 281 } 282 stopPrefixDiscovery()283 private void stopPrefixDiscovery() { 284 try { 285 mDnsResolver.stopPrefix64Discovery(getNetId()); 286 } catch (RemoteException | ServiceSpecificException e) { 287 Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e); 288 } 289 } 290 291 /** 292 * Starts/stops NAT64 prefix discovery and clatd as necessary. 293 */ update()294 public void update() { 295 // TODO: turn this class into a proper StateMachine. // http://b/126113090 296 if (requiresClat(mNetwork)) { 297 if (!isPrefixDiscoveryStarted()) { 298 startPrefixDiscovery(); 299 } else if (shouldStartClat(mNetwork)) { 300 // NAT64 prefix detected. Start clatd. 301 // TODO: support the NAT64 prefix changing after it's been discovered. There is no 302 // need to support this at the moment because it cannot happen without changes to 303 // the Dns64Configuration code in netd. 304 start(); 305 } else { 306 // NAT64 prefix removed. Stop clatd and go back into DISCOVERING state. 307 stop(); 308 } 309 } else { 310 // Network no longer requires clat. Stop clat and prefix discovery. 311 if (isStarted()) { 312 stop(); 313 } else if (isPrefixDiscoveryStarted()) { 314 leaveStartedState(); 315 } 316 } 317 } 318 setNat64Prefix(IpPrefix nat64Prefix)319 public void setNat64Prefix(IpPrefix nat64Prefix) { 320 mNat64Prefix = nat64Prefix; 321 } 322 323 /** 324 * Copies the stacked clat link in oldLp, if any, to the passed LinkProperties. 325 * This is necessary because the LinkProperties in mNetwork come from the transport layer, which 326 * has no idea that 464xlat is running on top of it. 327 */ fixupLinkProperties(LinkProperties oldLp, LinkProperties lp)328 public void fixupLinkProperties(LinkProperties oldLp, LinkProperties lp) { 329 lp.setNat64Prefix(mNat64Prefix); 330 331 if (!isRunning()) { 332 return; 333 } 334 if (lp == null || lp.getAllInterfaceNames().contains(mIface)) { 335 return; 336 } 337 338 Slog.d(TAG, "clatd running, updating NAI for " + mIface); 339 for (LinkProperties stacked: oldLp.getStackedLinks()) { 340 if (Objects.equals(mIface, stacked.getInterfaceName())) { 341 lp.addStackedLink(stacked); 342 return; 343 } 344 } 345 } 346 makeLinkProperties(LinkAddress clatAddress)347 private LinkProperties makeLinkProperties(LinkAddress clatAddress) { 348 LinkProperties stacked = new LinkProperties(); 349 stacked.setInterfaceName(mIface); 350 351 // Although the clat interface is a point-to-point tunnel, we don't 352 // point the route directly at the interface because some apps don't 353 // understand routes without gateways (see, e.g., http://b/9597256 354 // http://b/9597516). Instead, set the next hop of the route to the 355 // clat IPv4 address itself (for those apps, it doesn't matter what 356 // the IP of the gateway is, only that there is one). 357 RouteInfo ipv4Default = new RouteInfo( 358 new LinkAddress(Inet4Address.ANY, 0), 359 clatAddress.getAddress(), mIface); 360 stacked.addRoute(ipv4Default); 361 stacked.addLinkAddress(clatAddress); 362 return stacked; 363 } 364 getLinkAddress(String iface)365 private LinkAddress getLinkAddress(String iface) { 366 try { 367 InterfaceConfiguration config = mNMService.getInterfaceConfig(iface); 368 return config.getLinkAddress(); 369 } catch (RemoteException | IllegalStateException e) { 370 Slog.e(TAG, "Error getting link properties: " + e); 371 return null; 372 } 373 } 374 375 /** 376 * Adds stacked link on base link and transitions to RUNNING state. 377 */ handleInterfaceLinkStateChanged(String iface, boolean up)378 private void handleInterfaceLinkStateChanged(String iface, boolean up) { 379 // TODO: if we call start(), then stop(), then start() again, and the 380 // interfaceLinkStateChanged notification for the first start is delayed past the first 381 // stop, then the code becomes out of sync with system state and will behave incorrectly. 382 // 383 // This is not trivial to fix because: 384 // 1. It is not guaranteed that start() will eventually result in the interface coming up, 385 // because there could be an error starting clat (e.g., if the interface goes down before 386 // the packet socket can be bound). 387 // 2. If start is called multiple times, there is nothing in the interfaceLinkStateChanged 388 // notification that says which start() call the interface was created by. 389 // 390 // Once this code is converted to StateMachine, it will be possible to use deferMessage to 391 // ensure it stays in STARTING state until the interfaceLinkStateChanged notification fires, 392 // and possibly use a timeout (or provide some guarantees at the lower layer) to address #1. 393 if (!isStarting() || !up || !Objects.equals(mIface, iface)) { 394 return; 395 } 396 397 LinkAddress clatAddress = getLinkAddress(iface); 398 if (clatAddress == null) { 399 Slog.e(TAG, "clatAddress was null for stacked iface " + iface); 400 return; 401 } 402 403 Slog.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s", 404 mIface, mIface, mBaseIface)); 405 enterRunningState(); 406 LinkProperties lp = new LinkProperties(mNetwork.linkProperties); 407 lp.addStackedLink(makeLinkProperties(clatAddress)); 408 mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp); 409 } 410 411 /** 412 * Removes stacked link on base link and transitions to IDLE state. 413 */ handleInterfaceRemoved(String iface)414 private void handleInterfaceRemoved(String iface) { 415 if (!Objects.equals(mIface, iface)) { 416 return; 417 } 418 if (!isRunning()) { 419 return; 420 } 421 422 Slog.i(TAG, "interface " + iface + " removed"); 423 // If we're running, and the interface was removed, then we didn't call stop(), and it's 424 // likely that clatd crashed. Ensure we call stop() so we can start clatd again. Calling 425 // stop() will also update LinkProperties, and if clatd crashed, the LinkProperties update 426 // will cause ConnectivityService to call start() again. 427 stop(); 428 } 429 430 @Override interfaceLinkStateChanged(String iface, boolean up)431 public void interfaceLinkStateChanged(String iface, boolean up) { 432 mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); }); 433 } 434 435 @Override interfaceRemoved(String iface)436 public void interfaceRemoved(String iface) { 437 mNetwork.handler().post(() -> { handleInterfaceRemoved(iface); }); 438 } 439 440 @Override toString()441 public String toString() { 442 return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState; 443 } 444 445 @VisibleForTesting getNetId()446 protected int getNetId() { 447 return mNetwork.network.netId; 448 } 449 } 450