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