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