Nat464Xlat.java revision 771d5c2f0126ba692897c9716f4098ae6e3a870c
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.RouteInfo; 24import android.os.INetworkManagementService; 25import android.os.RemoteException; 26import android.util.Slog; 27 28import com.android.internal.util.ArrayUtils; 29import com.android.server.net.BaseNetworkObserver; 30 31import java.net.Inet4Address; 32import java.util.Objects; 33 34/** 35 * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated 36 * from a consistent and unique thread context. It is the responsability of ConnectivityService to 37 * call into this class from its own Handler thread. 38 * 39 * @hide 40 */ 41public class Nat464Xlat extends BaseNetworkObserver { 42 private static final String TAG = Nat464Xlat.class.getSimpleName(); 43 44 // This must match the interface prefix in clatd.c. 45 private static final String CLAT_PREFIX = "v4-"; 46 47 // The network types we will start clatd on, 48 // allowing clat only on networks for which we can support IPv6-only. 49 private static final int[] NETWORK_TYPES = { 50 ConnectivityManager.TYPE_MOBILE, 51 ConnectivityManager.TYPE_WIFI, 52 ConnectivityManager.TYPE_ETHERNET, 53 }; 54 55 private final INetworkManagementService mNMService; 56 57 // The network we're running on, and its type. 58 private final NetworkAgentInfo mNetwork; 59 60 private enum State { 61 IDLE, // start() not called. Base iface and stacked iface names are null. 62 STARTING, // start() called. Base iface and stacked iface names are known. 63 RUNNING; // start() called, and the stacked iface is known to be up. 64 } 65 66 private String mBaseIface; 67 private String mIface; 68 private State mState = State.IDLE; 69 70 public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) { 71 mNMService = nmService; 72 mNetwork = nai; 73 } 74 75 /** 76 * Determines whether a network requires clat. 77 * @param network the NetworkAgentInfo corresponding to the network. 78 * @return true if the network requires clat, false otherwise. 79 */ 80 public static boolean requiresClat(NetworkAgentInfo nai) { 81 // TODO: migrate to NetworkCapabilities.TRANSPORT_*. 82 final int netType = nai.networkInfo.getType(); 83 final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType()); 84 final boolean connected = nai.networkInfo.isConnected(); 85 // We only run clat on networks that don't have a native IPv4 address. 86 final boolean hasIPv4Address = 87 (nai.linkProperties != null) && nai.linkProperties.hasIPv4Address(); 88 return supported && connected && !hasIPv4Address; 89 } 90 91 /** 92 * @return true if clatd has been started and has not yet stopped. 93 * A true result corresponds to internal states STARTING and RUNNING. 94 */ 95 public boolean isStarted() { 96 return mState != State.IDLE; 97 } 98 99 /** 100 * @return true if clatd has been started but the stacked interface is not yet up. 101 */ 102 public boolean isStarting() { 103 return mState == State.STARTING; 104 } 105 106 /** 107 * @return true if clatd has been started and the stacked interface is up. 108 */ 109 public boolean isRunning() { 110 return mState == State.RUNNING; 111 } 112 113 /** 114 * Sets internal state. 115 */ 116 private void enterStartingState(String baseIface) { 117 mIface = CLAT_PREFIX + baseIface; 118 mBaseIface = baseIface; 119 mState = State.STARTING; 120 } 121 122 /** 123 * Clears internal state. 124 */ 125 private void enterIdleState() { 126 mIface = null; 127 mBaseIface = null; 128 mState = State.IDLE; 129 } 130 131 /** 132 * Starts the clat daemon. 133 */ 134 public void start() { 135 if (isStarted()) { 136 Slog.e(TAG, "startClat: already started"); 137 return; 138 } 139 140 if (mNetwork.linkProperties == null) { 141 Slog.e(TAG, "startClat: Can't start clat with null LinkProperties"); 142 return; 143 } 144 145 try { 146 mNMService.registerObserver(this); 147 } catch(RemoteException e) { 148 Slog.e(TAG, "startClat: Can't register interface observer for clat on " + mNetwork); 149 return; 150 } 151 152 String baseIface = mNetwork.linkProperties.getInterfaceName(); 153 if (baseIface == null) { 154 Slog.e(TAG, "startClat: Can't start clat on null interface"); 155 return; 156 } 157 // TODO: should we only do this if mNMService.startClatd() succeeds? 158 enterStartingState(baseIface); 159 160 Slog.i(TAG, "Starting clatd on " + mBaseIface); 161 try { 162 mNMService.startClatd(mBaseIface); 163 } catch(RemoteException|IllegalStateException e) { 164 Slog.e(TAG, "Error starting clatd on " + mBaseIface, e); 165 } 166 } 167 168 /** 169 * Stops the clat daemon. 170 */ 171 public void stop() { 172 if (!isStarted()) { 173 Slog.e(TAG, "stopClat: already stopped or not started"); 174 return; 175 } 176 177 Slog.i(TAG, "Stopping clatd on " + mBaseIface); 178 try { 179 mNMService.stopClatd(mBaseIface); 180 } catch(RemoteException|IllegalStateException e) { 181 Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e); 182 } 183 // When clatd stops and its interface is deleted, handleInterfaceRemoved() will trigger 184 // ConnectivityService#handleUpdateLinkProperties and call enterIdleState(). 185 } 186 187 /** 188 * Copies the stacked clat link in oldLp, if any, to the LinkProperties in mNetwork. 189 * This is necessary because the LinkProperties in mNetwork come from the transport layer, which 190 * has no idea that 464xlat is running on top of it. 191 */ 192 public void fixupLinkProperties(LinkProperties oldLp) { 193 if (!isRunning()) { 194 return; 195 } 196 LinkProperties lp = mNetwork.linkProperties; 197 if (lp == null || lp.getAllInterfaceNames().contains(mIface)) { 198 return; 199 } 200 201 Slog.d(TAG, "clatd running, updating NAI for " + mIface); 202 for (LinkProperties stacked: oldLp.getStackedLinks()) { 203 if (Objects.equals(mIface, stacked.getInterfaceName())) { 204 lp.addStackedLink(stacked); 205 return; 206 } 207 } 208 } 209 210 private LinkProperties makeLinkProperties(LinkAddress clatAddress) { 211 LinkProperties stacked = new LinkProperties(); 212 stacked.setInterfaceName(mIface); 213 214 // Although the clat interface is a point-to-point tunnel, we don't 215 // point the route directly at the interface because some apps don't 216 // understand routes without gateways (see, e.g., http://b/9597256 217 // http://b/9597516). Instead, set the next hop of the route to the 218 // clat IPv4 address itself (for those apps, it doesn't matter what 219 // the IP of the gateway is, only that there is one). 220 RouteInfo ipv4Default = new RouteInfo( 221 new LinkAddress(Inet4Address.ANY, 0), 222 clatAddress.getAddress(), mIface); 223 stacked.addRoute(ipv4Default); 224 stacked.addLinkAddress(clatAddress); 225 return stacked; 226 } 227 228 private LinkAddress getLinkAddress(String iface) { 229 try { 230 InterfaceConfiguration config = mNMService.getInterfaceConfig(iface); 231 return config.getLinkAddress(); 232 } catch(RemoteException|IllegalStateException e) { 233 Slog.e(TAG, "Error getting link properties: " + e); 234 return null; 235 } 236 } 237 238 private void maybeSetIpv6NdOffload(String iface, boolean on) { 239 // TODO: migrate to NetworkCapabilities.TRANSPORT_*. 240 if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) { 241 return; 242 } 243 try { 244 Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface); 245 mNMService.setInterfaceIpv6NdOffload(iface, on); 246 } catch(RemoteException|IllegalStateException e) { 247 Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e); 248 } 249 } 250 251 /** 252 * Adds stacked link on base link and transitions to RUNNING state. 253 */ 254 private void handleInterfaceLinkStateChanged(String iface, boolean up) { 255 if (!isStarting() || !up || !Objects.equals(mIface, iface)) { 256 return; 257 } 258 LinkAddress clatAddress = getLinkAddress(iface); 259 if (clatAddress == null) { 260 Slog.e(TAG, "cladAddress was null for stacked iface " + iface); 261 return; 262 } 263 mState = State.RUNNING; 264 Slog.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s", 265 mIface, mIface, mBaseIface)); 266 267 maybeSetIpv6NdOffload(mBaseIface, false); 268 LinkProperties lp = new LinkProperties(mNetwork.linkProperties); 269 lp.addStackedLink(makeLinkProperties(clatAddress)); 270 mNetwork.connService.handleUpdateLinkProperties(mNetwork, lp); 271 } 272 273 /** 274 * Removes stacked link on base link and transitions to IDLE state. 275 */ 276 private void handleInterfaceRemoved(String iface) { 277 if (!isRunning() || !Objects.equals(mIface, iface)) { 278 return; 279 } 280 281 Slog.i(TAG, "interface " + iface + " removed"); 282 // The interface going away likely means clatd has crashed. Ask netd to stop it, 283 // because otherwise when we try to start it again on the same base interface netd 284 // will complain that it's already started. 285 try { 286 mNMService.unregisterObserver(this); 287 // TODO: add STOPPING state to avoid calling stopClatd twice. 288 mNMService.stopClatd(mBaseIface); 289 } catch(RemoteException|IllegalStateException e) { 290 Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e); 291 } 292 maybeSetIpv6NdOffload(mBaseIface, true); 293 LinkProperties lp = new LinkProperties(mNetwork.linkProperties); 294 lp.removeStackedLink(mIface); 295 enterIdleState(); 296 mNetwork.connService.handleUpdateLinkProperties(mNetwork, lp); 297 } 298 299 @Override 300 public void interfaceLinkStateChanged(String iface, boolean up) { 301 mNetwork.handler.post(() -> { handleInterfaceLinkStateChanged(iface, up); }); 302 } 303 304 @Override 305 public void interfaceRemoved(String iface) { 306 mNetwork.handler.post(() -> { handleInterfaceRemoved(iface); }); 307 } 308 309 @Override 310 public String toString() { 311 return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState; 312 } 313} 314