diff options
Diffstat (limited to 'android/net')
-rw-r--r-- | android/net/IpSecAlgorithm.java | 24 | ||||
-rw-r--r-- | android/net/IpSecConfig.java | 20 | ||||
-rw-r--r-- | android/net/IpSecTransform.java | 27 | ||||
-rw-r--r-- | android/net/LinkProperties.java | 30 | ||||
-rw-r--r-- | android/net/apf/ApfFilter.java | 16 | ||||
-rw-r--r-- | android/net/ip/ConnectivityPacketTracker.java | 23 | ||||
-rw-r--r-- | android/net/ip/IpClient.java | 1712 | ||||
-rw-r--r-- | android/net/ip/IpManager.java | 1689 | ||||
-rw-r--r-- | android/net/metrics/ConnectStats.java | 11 | ||||
-rw-r--r-- | android/net/metrics/DnsEvent.java | 21 | ||||
-rw-r--r-- | android/net/metrics/NetworkMetrics.java | 168 | ||||
-rw-r--r-- | android/net/netlink/NetlinkSocket.java | 2 | ||||
-rw-r--r-- | android/net/util/SharedLog.java | 4 | ||||
-rw-r--r-- | android/net/wifi/WifiManager.java | 20 | ||||
-rw-r--r-- | android/net/wifi/rtt/RangingRequest.java | 173 | ||||
-rw-r--r-- | android/net/wifi/rtt/RangingResult.java | 167 | ||||
-rw-r--r-- | android/net/wifi/rtt/RangingResultCallback.java | 30 | ||||
-rw-r--r-- | android/net/wifi/rtt/WifiRttManager.java | 21 |
18 files changed, 2425 insertions, 1733 deletions
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java index 79310e29..16b14523 100644 --- a/android/net/IpSecAlgorithm.java +++ b/android/net/IpSecAlgorithm.java @@ -31,7 +31,6 @@ import java.util.Arrays; * RFC 4301. */ public final class IpSecAlgorithm implements Parcelable { - /** * AES-CBC Encryption/Ciphering Algorithm. * @@ -68,6 +67,7 @@ public final class IpSecAlgorithm implements Parcelable { * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384. */ public static final String AUTH_HMAC_SHA384 = "hmac(sha384)"; + /** * SHA512 HMAC Authentication/Integrity Algorithm * @@ -75,8 +75,24 @@ public final class IpSecAlgorithm implements Parcelable { */ public static final String AUTH_HMAC_SHA512 = "hmac(sha512)"; + /** + * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm. + * + * <p>Valid lengths for this key are {128, 192, 256}. + * + * <p>Valid ICV (truncation) lengths are {64, 96, 128}. + */ + public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))"; + /** @hide */ - @StringDef({CRYPT_AES_CBC, AUTH_HMAC_MD5, AUTH_HMAC_SHA1, AUTH_HMAC_SHA256, AUTH_HMAC_SHA512}) + @StringDef({ + CRYPT_AES_CBC, + AUTH_HMAC_MD5, + AUTH_HMAC_SHA1, + AUTH_HMAC_SHA256, + AUTH_HMAC_SHA512, + AUTH_CRYPT_AES_GCM + }) @Retention(RetentionPolicy.SOURCE) public @interface AlgorithmName {} @@ -102,7 +118,7 @@ public final class IpSecAlgorithm implements Parcelable { * @param algoName precise name of the algorithm to be used. * @param key non-null Key padded to a multiple of 8 bits. * @param truncLenBits the number of bits of output hash to use; only meaningful for - * Authentication. + * Authentication or Authenticated Encryption (equivalent to ICV length). */ public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) { if (!isTruncationLengthValid(algoName, truncLenBits)) { @@ -175,6 +191,8 @@ public final class IpSecAlgorithm implements Parcelable { return (truncLenBits >= 192 && truncLenBits <= 384); case AUTH_HMAC_SHA512: return (truncLenBits >= 256 && truncLenBits <= 512); + case AUTH_CRYPT_AES_GCM: + return (truncLenBits == 64 || truncLenBits == 96 || truncLenBits == 128); default: return false; } diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java index 632b7fc0..61b13a92 100644 --- a/android/net/IpSecConfig.java +++ b/android/net/IpSecConfig.java @@ -50,6 +50,9 @@ public final class IpSecConfig implements Parcelable { // Authentication Algorithm private IpSecAlgorithm mAuthentication; + // Authenticated Encryption Algorithm + private IpSecAlgorithm mAuthenticatedEncryption; + @Override public String toString() { return new StringBuilder() @@ -59,6 +62,8 @@ public final class IpSecConfig implements Parcelable { .append(mEncryption) .append(", mAuthentication=") .append(mAuthentication) + .append(", mAuthenticatedEncryption=") + .append(mAuthenticatedEncryption) .append("}") .toString(); } @@ -118,6 +123,11 @@ public final class IpSecConfig implements Parcelable { mFlow[direction].mAuthentication = authentication; } + /** Set the authenticated encryption algorithm for a given direction */ + public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) { + mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption; + } + public void setNetwork(Network network) { mNetwork = network; } @@ -163,6 +173,10 @@ public final class IpSecConfig implements Parcelable { return mFlow[direction].mAuthentication; } + public IpSecAlgorithm getAuthenticatedEncryption(int direction) { + return mFlow[direction].mAuthenticatedEncryption; + } + public Network getNetwork() { return mNetwork; } @@ -199,9 +213,11 @@ public final class IpSecConfig implements Parcelable { out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId); out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags); out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags); + out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags); out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId); out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags); out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags); + out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags); out.writeInt(mEncapType); out.writeInt(mEncapSocketResourceId); out.writeInt(mEncapRemotePort); @@ -221,11 +237,15 @@ public final class IpSecConfig implements Parcelable { (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); mFlow[IpSecTransform.DIRECTION_IN].mAuthentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt(); mFlow[IpSecTransform.DIRECTION_OUT].mEncryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); + mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption = + (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); mEncapType = in.readInt(); mEncapSocketResourceId = in.readInt(); mEncapRemotePort = in.readInt(); diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java index e15a2c67..48b5bd5c 100644 --- a/android/net/IpSecTransform.java +++ b/android/net/IpSecTransform.java @@ -281,6 +281,8 @@ public final class IpSecTransform implements AutoCloseable { * <p>If encryption is set for a given direction without also providing an SPI for that * direction, creation of an IpSecTransform will fail upon calling a build() method. * + * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. */ @@ -296,6 +298,8 @@ public final class IpSecTransform implements AutoCloseable { * <p>If authentication is set for a given direction without also providing an SPI for that * direction, creation of an IpSecTransform will fail upon calling a build() method. * + * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. */ @@ -306,6 +310,29 @@ public final class IpSecTransform implements AutoCloseable { } /** + * Add an authenticated encryption algorithm to the transform for the given direction. + * + * <p>If an authenticated encryption algorithm is set for a given direction without also + * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling + * a build() method. + * + * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated + * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as + * referred to in RFC 4301) + * + * <p>Authenticated encryption is mutually exclusive with encryption and authentication. + * + * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} + * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to + * be applied. + */ + public IpSecTransform.Builder setAuthenticatedEncryption( + @TransformDirection int direction, IpSecAlgorithm algo) { + mConfig.setAuthenticatedEncryption(direction, algo); + return this; + } + + /** * Set the SPI, which uniquely identifies a particular IPsec session from others. Because * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a * given destination address. diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java index 2c9fb23e..4e474c8e 100644 --- a/android/net/LinkProperties.java +++ b/android/net/LinkProperties.java @@ -683,9 +683,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv4Address() { for (LinkAddress address : mLinkAddresses) { - if (address.getAddress() instanceof Inet4Address) { - return true; - } + if (address.getAddress() instanceof Inet4Address) { + return true; + } } return false; } @@ -725,9 +725,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv4DefaultRoute() { for (RouteInfo r : mRoutes) { - if (r.isIPv4Default()) { - return true; - } + if (r.isIPv4Default()) { + return true; + } } return false; } @@ -740,9 +740,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv6DefaultRoute() { for (RouteInfo r : mRoutes) { - if (r.isIPv6Default()) { - return true; - } + if (r.isIPv6Default()) { + return true; + } } return false; } @@ -755,9 +755,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv4DnsServer() { for (InetAddress ia : mDnses) { - if (ia instanceof Inet4Address) { - return true; - } + if (ia instanceof Inet4Address) { + return true; + } } return false; } @@ -770,9 +770,9 @@ public final class LinkProperties implements Parcelable { */ public boolean hasIPv6DnsServer() { for (InetAddress ia : mDnses) { - if (ia instanceof Inet6Address) { - return true; - } + if (ia instanceof Inet6Address) { + return true; + } } return false; } diff --git a/android/net/apf/ApfFilter.java b/android/net/apf/ApfFilter.java index 190b3a61..5c2b66f6 100644 --- a/android/net/apf/ApfFilter.java +++ b/android/net/apf/ApfFilter.java @@ -33,7 +33,7 @@ import android.net.NetworkUtils; import android.net.apf.ApfGenerator; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; -import android.net.ip.IpManager; +import android.net.ip.IpClient; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; import android.net.metrics.IpConnectivityLog; @@ -238,7 +238,7 @@ public class ApfFilter { private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; private final ApfCapabilities mApfCapabilities; - private final IpManager.Callback mIpManagerCallback; + private final IpClient.Callback mIpClientCallback; private final NetworkInterface mNetworkInterface; private final IpConnectivityLog mMetricsLog; @@ -262,10 +262,10 @@ public class ApfFilter { @VisibleForTesting ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface, - IpManager.Callback ipManagerCallback, boolean multicastFilter, + IpClient.Callback ipClientCallback, boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) { mApfCapabilities = apfCapabilities; - mIpManagerCallback = ipManagerCallback; + mIpClientCallback = ipClientCallback; mNetworkInterface = networkInterface; mMulticastFilter = multicastFilter; mDrop802_3Frames = ieee802_3Filter; @@ -275,7 +275,7 @@ public class ApfFilter { mMetricsLog = log; - // TODO: ApfFilter should not generate programs until IpManager sends provisioning success. + // TODO: ApfFilter should not generate programs until IpClient sends provisioning success. maybeStartFilter(); } @@ -1051,7 +1051,7 @@ public class ApfFilter { if (VDBG) { hexDump("Installing filter: ", program, program.length); } - mIpManagerCallback.installPacketFilter(program); + mIpClientCallback.installPacketFilter(program); logApfProgramEventLocked(now); mLastInstallEvent = new ApfProgramEvent(); mLastInstallEvent.lifetime = programMinLifetime; @@ -1161,7 +1161,7 @@ public class ApfFilter { * filtering using APF programs. */ public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities, - NetworkInterface networkInterface, IpManager.Callback ipManagerCallback, + NetworkInterface networkInterface, IpClient.Callback ipClientCallback, boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) { if (apfCapabilities == null || networkInterface == null) return null; if (apfCapabilities.apfVersionSupported == 0) return null; @@ -1178,7 +1178,7 @@ public class ApfFilter { Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); return null; } - return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback, + return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback, multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog()); } diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java index 0230f36b..1925c39e 100644 --- a/android/net/ip/ConnectivityPacketTracker.java +++ b/android/net/ip/ConnectivityPacketTracker.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.system.ErrnoException; import android.system.Os; import android.system.PacketSocketAddress; +import android.text.TextUtils; import android.util.Log; import android.util.LocalLog; @@ -59,11 +60,14 @@ public class ConnectivityPacketTracker { private static final boolean DBG = false; private static final String MARK_START = "--- START ---"; private static final String MARK_STOP = "--- STOP ---"; + private static final String MARK_NAMED_START = "--- START (%s) ---"; + private static final String MARK_NAMED_STOP = "--- STOP (%s) ---"; private final String mTag; private final LocalLog mLog; private final BlockingSocketReader mPacketListener; private boolean mRunning; + private String mDisplayName; public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) { final String ifname; @@ -85,14 +89,16 @@ public class ConnectivityPacketTracker { mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu); } - public void start() { + public void start(String displayName) { mRunning = true; + mDisplayName = displayName; mPacketListener.start(); } public void stop() { mPacketListener.stop(); mRunning = false; + mDisplayName = null; } private final class PacketListener extends BlockingSocketReader { @@ -133,16 +139,19 @@ public class ConnectivityPacketTracker { @Override protected void onStart() { - mLog.log(MARK_START); + final String msg = TextUtils.isEmpty(mDisplayName) + ? MARK_START + : String.format(MARK_NAMED_START, mDisplayName); + mLog.log(msg); } @Override protected void onStop() { - if (mRunning) { - mLog.log(MARK_STOP); - } else { - mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)"); - } + String msg = TextUtils.isEmpty(mDisplayName) + ? MARK_STOP + : String.format(MARK_NAMED_STOP, mDisplayName); + if (!mRunning) msg += " (packet listener stopped unexpectedly)"; + mLog.log(msg); } @Override diff --git a/android/net/ip/IpClient.java b/android/net/ip/IpClient.java new file mode 100644 index 00000000..2359fab4 --- /dev/null +++ b/android/net/ip/IpClient.java @@ -0,0 +1,1712 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ip; + +import com.android.internal.util.MessageUtils; +import com.android.internal.util.WakeupMessage; + +import android.content.Context; +import android.net.DhcpResults; +import android.net.INetd; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties.ProvisioningChange; +import android.net.LinkProperties; +import android.net.Network; +import android.net.ProxyInfo; +import android.net.RouteInfo; +import android.net.StaticIpConfiguration; +import android.net.apf.ApfCapabilities; +import android.net.apf.ApfFilter; +import android.net.dhcp.DhcpClient; +import android.net.metrics.IpConnectivityLog; +import android.net.metrics.IpManagerEvent; +import android.net.util.MultinetworkPolicyTracker; +import android.net.util.NetdService; +import android.net.util.NetworkConstants; +import android.net.util.SharedLog; +import android.os.INetworkManagementService; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.LocalLog; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.R; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.IState; +import com.android.internal.util.Preconditions; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.server.net.NetlinkTracker; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import java.util.function.Predicate; +import java.util.stream.Collectors; + + +/** + * IpClient + * + * This class provides the interface to IP-layer provisioning and maintenance + * functionality that can be used by transport layers like Wi-Fi, Ethernet, + * et cetera. + * + * [ Lifetime ] + * IpClient is designed to be instantiated as soon as the interface name is + * known and can be as long-lived as the class containing it (i.e. declaring + * it "private final" is okay). + * + * @hide + */ +public class IpClient extends StateMachine { + private static final boolean DBG = false; + + // For message logging. + private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class }; + private static final SparseArray<String> sWhatToString = + MessageUtils.findMessageNames(sMessageClasses); + + /** + * Callbacks for handling IpClient events. + */ + public static class Callback { + // In order to receive onPreDhcpAction(), call #withPreDhcpAction() + // when constructing a ProvisioningConfiguration. + // + // Implementations of onPreDhcpAction() must call + // IpClient#completedPreDhcpAction() to indicate that DHCP is clear + // to proceed. + public void onPreDhcpAction() {} + public void onPostDhcpAction() {} + + // This is purely advisory and not an indication of provisioning + // success or failure. This is only here for callers that want to + // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress). + // DHCPv4 or static IPv4 configuration failure or success can be + // determined by whether or not the passed-in DhcpResults object is + // null or not. + public void onNewDhcpResults(DhcpResults dhcpResults) {} + + public void onProvisioningSuccess(LinkProperties newLp) {} + public void onProvisioningFailure(LinkProperties newLp) {} + + // Invoked on LinkProperties changes. + public void onLinkPropertiesChange(LinkProperties newLp) {} + + // Called when the internal IpReachabilityMonitor (if enabled) has + // detected the loss of a critical number of required neighbors. + public void onReachabilityLost(String logMsg) {} + + // Called when the IpClient state machine terminates. + public void onQuit() {} + + // Install an APF program to filter incoming packets. + public void installPacketFilter(byte[] filter) {} + + // If multicast filtering cannot be accomplished with APF, this function will be called to + // actuate multicast filtering using another means. + public void setFallbackMulticastFilter(boolean enabled) {} + + // Enabled/disable Neighbor Discover offload functionality. This is + // called, for example, whenever 464xlat is being started or stopped. + public void setNeighborDiscoveryOffload(boolean enable) {} + } + + // Use a wrapper class to log in order to ensure complete and detailed + // logging. This method is lighter weight than annotations/reflection + // and has the following benefits: + // + // - No invoked method can be forgotten. + // Any new method added to IpClient.Callback must be overridden + // here or it will never be called. + // + // - No invoking call site can be forgotten. + // Centralized logging in this way means call sites don't need to + // remember to log, and therefore no call site can be forgotten. + // + // - No variation in log format among call sites. + // Encourages logging of any available arguments, and all call sites + // are necessarily logged identically. + // + // TODO: Find an lighter weight approach. + private class LoggingCallbackWrapper extends Callback { + private static final String PREFIX = "INVOKE "; + private Callback mCallback; + + public LoggingCallbackWrapper(Callback callback) { + mCallback = callback; + } + + private void log(String msg) { + mLog.log(PREFIX + msg); + } + + @Override + public void onPreDhcpAction() { + mCallback.onPreDhcpAction(); + log("onPreDhcpAction()"); + } + @Override + public void onPostDhcpAction() { + mCallback.onPostDhcpAction(); + log("onPostDhcpAction()"); + } + @Override + public void onNewDhcpResults(DhcpResults dhcpResults) { + mCallback.onNewDhcpResults(dhcpResults); + log("onNewDhcpResults({" + dhcpResults + "})"); + } + @Override + public void onProvisioningSuccess(LinkProperties newLp) { + mCallback.onProvisioningSuccess(newLp); + log("onProvisioningSuccess({" + newLp + "})"); + } + @Override + public void onProvisioningFailure(LinkProperties newLp) { + mCallback.onProvisioningFailure(newLp); + log("onProvisioningFailure({" + newLp + "})"); + } + @Override + public void onLinkPropertiesChange(LinkProperties newLp) { + mCallback.onLinkPropertiesChange(newLp); + log("onLinkPropertiesChange({" + newLp + "})"); + } + @Override + public void onReachabilityLost(String logMsg) { + mCallback.onReachabilityLost(logMsg); + log("onReachabilityLost(" + logMsg + ")"); + } + @Override + public void onQuit() { + mCallback.onQuit(); + log("onQuit()"); + } + @Override + public void installPacketFilter(byte[] filter) { + mCallback.installPacketFilter(filter); + log("installPacketFilter(byte[" + filter.length + "])"); + } + @Override + public void setFallbackMulticastFilter(boolean enabled) { + mCallback.setFallbackMulticastFilter(enabled); + log("setFallbackMulticastFilter(" + enabled + ")"); + } + @Override + public void setNeighborDiscoveryOffload(boolean enable) { + mCallback.setNeighborDiscoveryOffload(enable); + log("setNeighborDiscoveryOffload(" + enable + ")"); + } + } + + /** + * This class encapsulates parameters to be passed to + * IpClient#startProvisioning(). A defensive copy is made by IpClient + * and the values specified herein are in force until IpClient#stop() + * is called. + * + * Example use: + * + * final ProvisioningConfiguration config = + * mIpClient.buildProvisioningConfiguration() + * .withPreDhcpAction() + * .withProvisioningTimeoutMs(36 * 1000) + * .build(); + * mIpClient.startProvisioning(config); + * ... + * mIpClient.stop(); + * + * The specified provisioning configuration will only be active until + * IpClient#stop() is called. Future calls to IpClient#startProvisioning() + * must specify the configuration again. + */ + public static class ProvisioningConfiguration { + // TODO: Delete this default timeout once those callers that care are + // fixed to pass in their preferred timeout. + // + // We pick 36 seconds so we can send DHCP requests at + // + // t=0, t=2, t=6, t=14, t=30 + // + // allowing for 10% jitter. + private static final int DEFAULT_TIMEOUT_MS = 36 * 1000; + + public static class Builder { + private ProvisioningConfiguration mConfig = new ProvisioningConfiguration(); + + public Builder withoutIPv4() { + mConfig.mEnableIPv4 = false; + return this; + } + + public Builder withoutIPv6() { + mConfig.mEnableIPv6 = false; + return this; + } + + public Builder withoutIpReachabilityMonitor() { + mConfig.mUsingIpReachabilityMonitor = false; + return this; + } + + public Builder withPreDhcpAction() { + mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS; + return this; + } + + public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { + mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs; + return this; + } + + public Builder withInitialConfiguration(InitialConfiguration initialConfig) { + mConfig.mInitialConfig = initialConfig; + return this; + } + + public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { + mConfig.mStaticIpConfig = staticConfig; + return this; + } + + public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { + mConfig.mApfCapabilities = apfCapabilities; + return this; + } + + public Builder withProvisioningTimeoutMs(int timeoutMs) { + mConfig.mProvisioningTimeoutMs = timeoutMs; + return this; + } + + public Builder withIPv6AddrGenModeEUI64() { + mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64; + return this; + } + + public Builder withIPv6AddrGenModeStablePrivacy() { + mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; + return this; + } + + public Builder withNetwork(Network network) { + mConfig.mNetwork = network; + return this; + } + + public Builder withDisplayName(String displayName) { + mConfig.mDisplayName = displayName; + return this; + } + + public ProvisioningConfiguration build() { + return new ProvisioningConfiguration(mConfig); + } + } + + /* package */ boolean mEnableIPv4 = true; + /* package */ boolean mEnableIPv6 = true; + /* package */ boolean mUsingIpReachabilityMonitor = true; + /* package */ int mRequestedPreDhcpActionMs; + /* package */ InitialConfiguration mInitialConfig; + /* package */ StaticIpConfiguration mStaticIpConfig; + /* package */ ApfCapabilities mApfCapabilities; + /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS; + /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; + /* package */ Network mNetwork = null; + /* package */ String mDisplayName = null; + + public ProvisioningConfiguration() {} // used by Builder + + public ProvisioningConfiguration(ProvisioningConfiguration other) { + mEnableIPv4 = other.mEnableIPv4; + mEnableIPv6 = other.mEnableIPv6; + mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor; + mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs; + mInitialConfig = InitialConfiguration.copy(other.mInitialConfig); + mStaticIpConfig = other.mStaticIpConfig; + mApfCapabilities = other.mApfCapabilities; + mProvisioningTimeoutMs = other.mProvisioningTimeoutMs; + mIPv6AddrGenMode = other.mIPv6AddrGenMode; + mNetwork = other.mNetwork; + mDisplayName = other.mDisplayName; + } + + @Override + public String toString() { + return new StringJoiner(", ", getClass().getSimpleName() + "{", "}") + .add("mEnableIPv4: " + mEnableIPv4) + .add("mEnableIPv6: " + mEnableIPv6) + .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor) + .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs) + .add("mInitialConfig: " + mInitialConfig) + .add("mStaticIpConfig: " + mStaticIpConfig) + .add("mApfCapabilities: " + mApfCapabilities) + .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs) + .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) + .add("mNetwork: " + mNetwork) + .add("mDisplayName: " + mDisplayName) + .toString(); + } + + public boolean isValid() { + return (mInitialConfig == null) || mInitialConfig.isValid(); + } + } + + public static class InitialConfiguration { + public final Set<LinkAddress> ipAddresses = new HashSet<>(); + public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>(); + public final Set<InetAddress> dnsServers = new HashSet<>(); + public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config + + public static InitialConfiguration copy(InitialConfiguration config) { + if (config == null) { + return null; + } + InitialConfiguration configCopy = new InitialConfiguration(); + configCopy.ipAddresses.addAll(config.ipAddresses); + configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes); + configCopy.dnsServers.addAll(config.dnsServers); + return configCopy; + } + + @Override + public String toString() { + return String.format( + "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)", + join(", ", ipAddresses), join(", ", directlyConnectedRoutes), + join(", ", dnsServers), gateway); + } + + public boolean isValid() { + if (ipAddresses.isEmpty()) { + return false; + } + + // For every IP address, there must be at least one prefix containing that address. + for (LinkAddress addr : ipAddresses) { + if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) { + return false; + } + } + // For every dns server, there must be at least one prefix containing that address. + for (InetAddress addr : dnsServers) { + if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) { + return false; + } + } + // All IPv6 LinkAddresses have an RFC7421-suitable prefix length + // (read: compliant with RFC4291#section2.5.4). + if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) { + return false; + } + // If directlyConnectedRoutes contains an IPv6 default route + // then ipAddresses MUST contain at least one non-ULA GUA. + if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute) + && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) { + return false; + } + // The prefix length of routes in directlyConnectedRoutes be within reasonable + // bounds for IPv6: /48-/64 just as we’d accept in RIOs. + if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) { + return false; + } + // There no more than one IPv4 address + if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) { + return false; + } + + return true; + } + + /** + * @return true if the given list of addressess and routes satisfies provisioning for this + * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality + * because addresses and routes seen by Netlink will contain additional fields like flags, + * interfaces, and so on. If this InitialConfiguration has no IP address specified, the + * provisioning check always fails. + * + * If the given list of routes is null, only addresses are taken into considerations. + */ + public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) { + if (ipAddresses.isEmpty()) { + return false; + } + + for (LinkAddress addr : ipAddresses) { + if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) { + return false; + } + } + + if (routes != null) { + for (IpPrefix prefix : directlyConnectedRoutes) { + if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) { + return false; + } + } + } + + return true; + } + + private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) { + return !route.hasGateway() && prefix.equals(route.getDestination()); + } + + private static boolean isPrefixLengthCompliant(LinkAddress addr) { + return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength()); + } + + private static boolean isPrefixLengthCompliant(IpPrefix prefix) { + return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); + } + + private static boolean isCompliantIPv6PrefixLength(int prefixLength) { + return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength) + && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH); + } + + private static boolean isIPv6DefaultRoute(IpPrefix prefix) { + return prefix.getAddress().equals(Inet6Address.ANY); + } + + private static boolean isIPv6GUA(LinkAddress addr) { + return addr.isIPv6() && addr.isGlobalPreferred(); + } + } + + public static final String DUMP_ARG = "ipclient"; + public static final String DUMP_ARG_CONFIRM = "confirm"; + + private static final int CMD_TERMINATE_AFTER_STOP = 1; + private static final int CMD_STOP = 2; + private static final int CMD_START = 3; + private static final int CMD_CONFIRM = 4; + private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; + // Sent by NetlinkTracker to communicate netlink events. + private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; + private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; + private static final int CMD_UPDATE_HTTP_PROXY = 8; + private static final int CMD_SET_MULTICAST_FILTER = 9; + private static final int EVENT_PROVISIONING_TIMEOUT = 10; + private static final int EVENT_DHCPACTION_TIMEOUT = 11; + + private static final int MAX_LOG_RECORDS = 500; + private static final int MAX_PACKET_RECORDS = 100; + + private static final boolean NO_CALLBACKS = false; + private static final boolean SEND_CALLBACKS = true; + + // This must match the interface prefix in clatd.c. + // TODO: Revert this hack once IpClient and Nat464Xlat work in concert. + private static final String CLAT_PREFIX = "v4-"; + + private final State mStoppedState = new StoppedState(); + private final State mStoppingState = new StoppingState(); + private final State mStartedState = new StartedState(); + private final State mRunningState = new RunningState(); + + private final String mTag; + private final Context mContext; + private final String mInterfaceName; + private final String mClatInterfaceName; + @VisibleForTesting + protected final Callback mCallback; + private final INetworkManagementService mNwService; + private final NetlinkTracker mNetlinkTracker; + private final WakeupMessage mProvisioningTimeoutAlarm; + private final WakeupMessage mDhcpActionTimeoutAlarm; + private final MultinetworkPolicyTracker mMultinetworkPolicyTracker; + private final SharedLog mLog; + private final LocalLog mConnectivityPacketLog; + private final MessageHandlingLogger mMsgStateLogger; + private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); + private final InterfaceController mInterfaceCtrl; + + private NetworkInterface mNetworkInterface; + + /** + * Non-final member variables accessed only from within our StateMachine. + */ + private LinkProperties mLinkProperties; + private ProvisioningConfiguration mConfiguration; + private IpReachabilityMonitor mIpReachabilityMonitor; + private DhcpClient mDhcpClient; + private DhcpResults mDhcpResults; + private String mTcpBufferSizes; + private ProxyInfo mHttpProxy; + private ApfFilter mApfFilter; + private boolean mMulticastFiltering; + private long mStartTimeMillis; + + public IpClient(Context context, String ifName, Callback callback) { + this(context, ifName, callback, INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)), + NetdService.getInstance()); + } + + /** + * An expanded constructor, useful for dependency injection. + * TODO: migrate all test users to mock IpClient directly and remove this ctor. + */ + public IpClient(Context context, String ifName, Callback callback, + INetworkManagementService nwService) { + this(context, ifName, callback, nwService, NetdService.getInstance()); + } + + @VisibleForTesting + IpClient(Context context, String ifName, Callback callback, + INetworkManagementService nwService, INetd netd) { + super(IpClient.class.getSimpleName() + "." + ifName); + mTag = getName(); + + mContext = context; + mInterfaceName = ifName; + mClatInterfaceName = CLAT_PREFIX + ifName; + mCallback = new LoggingCallbackWrapper(callback); + mNwService = nwService; + + mLog = new SharedLog(MAX_LOG_RECORDS, mTag); + mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS); + mMsgStateLogger = new MessageHandlingLogger(); + + mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog); + + mNetlinkTracker = new NetlinkTracker( + mInterfaceName, + new NetlinkTracker.Callback() { + @Override + public void update() { + sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); + } + }) { + @Override + public void interfaceAdded(String iface) { + super.interfaceAdded(iface); + if (mClatInterfaceName.equals(iface)) { + mCallback.setNeighborDiscoveryOffload(false); + } else if (!mInterfaceName.equals(iface)) { + return; + } + + final String msg = "interfaceAdded(" + iface +")"; + logMsg(msg); + } + + @Override + public void interfaceRemoved(String iface) { + super.interfaceRemoved(iface); + // TODO: Also observe mInterfaceName going down and take some + // kind of appropriate action. + if (mClatInterfaceName.equals(iface)) { + // TODO: consider sending a message to the IpClient main + // StateMachine thread, in case "NDO enabled" state becomes + // tied to more things that 464xlat operation. + mCallback.setNeighborDiscoveryOffload(true); + } else if (!mInterfaceName.equals(iface)) { + return; + } + + final String msg = "interfaceRemoved(" + iface +")"; + logMsg(msg); + } + + private void logMsg(String msg) { + Log.d(mTag, msg); + getHandler().post(() -> { mLog.log("OBSERVED " + msg); }); + } + }; + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + + mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(), + () -> { mLog.log("OBSERVED AvoidBadWifi changed"); }); + + mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); + mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); + + // Anything the StateMachine may access must have been instantiated + // before this point. + configureAndStartStateMachine(); + + // Anything that may send messages to the StateMachine must only be + // configured to do so after the StateMachine has started (above). + startStateMachineUpdaters(); + } + + private void configureAndStartStateMachine() { + addState(mStoppedState); + addState(mStartedState); + addState(mRunningState, mStartedState); + addState(mStoppingState); + + setInitialState(mStoppedState); + + super.start(); + } + + private void startStateMachineUpdaters() { + try { + mNwService.registerObserver(mNetlinkTracker); + } catch (RemoteException e) { + logError("Couldn't register NetlinkTracker: %s", e); + } + + mMultinetworkPolicyTracker.start(); + } + + private void stopStateMachineUpdaters() { + try { + mNwService.unregisterObserver(mNetlinkTracker); + } catch (RemoteException e) { + logError("Couldn't unregister NetlinkTracker: %s", e); + } + + mMultinetworkPolicyTracker.shutdown(); + } + + @Override + protected void onQuitting() { + mCallback.onQuit(); + } + + // Shut down this IpClient instance altogether. + public void shutdown() { + stop(); + sendMessage(CMD_TERMINATE_AFTER_STOP); + } + + public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { + return new ProvisioningConfiguration.Builder(); + } + + public void startProvisioning(ProvisioningConfiguration req) { + if (!req.isValid()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + return; + } + + getNetworkInterface(); + + mCallback.setNeighborDiscoveryOffload(true); + sendMessage(CMD_START, new ProvisioningConfiguration(req)); + } + + // TODO: Delete this. + public void startProvisioning(StaticIpConfiguration staticIpConfig) { + startProvisioning(buildProvisioningConfiguration() + .withStaticConfiguration(staticIpConfig) + .build()); + } + + public void startProvisioning() { + startProvisioning(new ProvisioningConfiguration()); + } + + public void stop() { + sendMessage(CMD_STOP); + } + + public void confirmConfiguration() { + sendMessage(CMD_CONFIRM); + } + + public void completedPreDhcpAction() { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + + /** + * Set the TCP buffer sizes to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setTcpBufferSizes(String tcpBufferSizes) { + sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); + } + + /** + * Set the HTTP Proxy configuration to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setHttpProxy(ProxyInfo proxyInfo) { + sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); + } + + /** + * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, + * if not, Callback.setFallbackMulticastFilter() is called. + */ + public void setMulticastFilter(boolean enabled) { + sendMessage(CMD_SET_MULTICAST_FILTER, enabled); + } + + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { + // Execute confirmConfiguration() and take no further action. + confirmConfiguration(); + return; + } + + // Thread-unsafe access to mApfFilter but just used for debugging. + final ApfFilter apfFilter = mApfFilter; + final ProvisioningConfiguration provisioningConfig = mConfiguration; + final ApfCapabilities apfCapabilities = (provisioningConfig != null) + ? provisioningConfig.mApfCapabilities : null; + + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println(mTag + " APF dump:"); + pw.increaseIndent(); + if (apfFilter != null) { + apfFilter.dump(pw); + } else { + pw.print("No active ApfFilter; "); + if (provisioningConfig == null) { + pw.println("IpClient not yet started."); + } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { + pw.println("Hardware does not support APF."); + } else { + pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); + } + } + pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " current ProvisioningConfiguration:"); + pw.increaseIndent(); + pw.println(Objects.toString(provisioningConfig, "N/A")); + pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " StateMachine dump:"); + pw.increaseIndent(); + mLog.dump(fd, pw, args); + pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " connectivity packet log:"); + pw.println(); + pw.println("Debug with python and scapy via:"); + pw.println("shell$ python"); + pw.println(">>> from scapy import all as scapy"); + pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); + pw.println(); + + pw.increaseIndent(); + mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); + pw.decreaseIndent(); + } + + + /** + * Internals. + */ + + @Override + protected String getWhatToString(int what) { + return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); + } + + @Override + protected String getLogRecString(Message msg) { + final String logLine = String.format( + "%s/%d %d %d %s [%s]", + mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(), + msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); + + final String richerLogLine = getWhatToString(msg.what) + " " + logLine; + mLog.log(richerLogLine); + if (DBG) { + Log.d(mTag, richerLogLine); + } + + mMsgStateLogger.reset(); + return logLine; + } + + @Override + protected boolean recordLogRec(Message msg) { + // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, + // and we already log any LinkProperties change that results in an + // invocation of IpClient.Callback#onLinkPropertiesChange(). + final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); + if (!shouldLog) { + mMsgStateLogger.reset(); + } + return shouldLog; + } + + private void logError(String fmt, Object... args) { + final String msg = "ERROR " + String.format(fmt, args); + Log.e(mTag, msg); + mLog.log(msg); + } + + private void getNetworkInterface() { + try { + mNetworkInterface = NetworkInterface.getByName(mInterfaceName); + } catch (SocketException | NullPointerException e) { + // TODO: throw new IllegalStateException. + logError("Failed to get interface object: %s", e); + } + } + + // This needs to be called with care to ensure that our LinkProperties + // are in sync with the actual LinkProperties of the interface. For example, + // we should only call this if we know for sure that there are no IP addresses + // assigned to the interface, etc. + private void resetLinkProperties() { + mNetlinkTracker.clearLinkProperties(); + mConfiguration = null; + mDhcpResults = null; + mTcpBufferSizes = ""; + mHttpProxy = null; + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + } + + private void recordMetric(final int type) { + if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); } + final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis; + mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); + } + + // For now: use WifiStateMachine's historical notion of provisioned. + @VisibleForTesting + static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) { + // For historical reasons, we should connect even if all we have is + // an IPv4 address and nothing else. + if (lp.hasIPv4Address() || lp.isProvisioned()) { + return true; + } + if (config == null) { + return false; + } + + // When an InitialConfiguration is specified, ignore any difference with previous + // properties and instead check if properties observed match the desired properties. + return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); + } + + // TODO: Investigate folding all this into the existing static function + // LinkProperties.compareProvisioning() or some other single function that + // takes two LinkProperties objects and returns a ProvisioningChange + // object that is a correct and complete assessment of what changed, taking + // account of the asymmetries described in the comments in this function. + // Then switch to using it everywhere (IpReachabilityMonitor, etc.). + private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { + ProvisioningChange delta; + InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; + final boolean wasProvisioned = isProvisioned(oldLp, config); + final boolean isProvisioned = isProvisioned(newLp, config); + + if (!wasProvisioned && isProvisioned) { + delta = ProvisioningChange.GAINED_PROVISIONING; + } else if (wasProvisioned && isProvisioned) { + delta = ProvisioningChange.STILL_PROVISIONED; + } else if (!wasProvisioned && !isProvisioned) { + delta = ProvisioningChange.STILL_NOT_PROVISIONED; + } else { + // (wasProvisioned && !isProvisioned) + // + // Note that this is true even if we lose a configuration element + // (e.g., a default gateway) that would not be required to advance + // into provisioned state. This is intended: if we have a default + // router and we lose it, that's a sure sign of a problem, but if + // we connect to a network with no IPv4 DNS servers, we consider + // that to be a network without DNS servers and connect anyway. + // + // See the comment below. + delta = ProvisioningChange.LOST_PROVISIONING; + } + + final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); + final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address(); + final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute(); + + // If bad wifi avoidance is disabled, then ignore IPv6 loss of + // provisioning. Otherwise, when a hotspot that loses Internet + // access sends out a 0-lifetime RA to its clients, the clients + // will disconnect and then reconnect, avoiding the bad hotspot, + // instead of getting stuck on the bad hotspot. http://b/31827713 . + // + // This is incorrect because if the hotspot then regains Internet + // access with a different prefix, TCP connections on the + // deprecated addresses will remain stuck. + // + // Note that we can still be disconnected by IpReachabilityMonitor + // if the IPv6 default gateway (but not the IPv6 DNS servers; see + // accompanying code in IpReachabilityMonitor) is unreachable. + final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi(); + + // Additionally: + // + // Partial configurations (e.g., only an IPv4 address with no DNS + // servers and no default route) are accepted as long as DHCPv4 + // succeeds. On such a network, isProvisioned() will always return + // false, because the configuration is not complete, but we want to + // connect anyway. It might be a disconnected network such as a + // Chromecast or a wireless printer, for example. + // + // Because on such a network isProvisioned() will always return false, + // delta will never be LOST_PROVISIONING. So check for loss of + // provisioning here too. + if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { + delta = ProvisioningChange.LOST_PROVISIONING; + } + + // Additionally: + // + // If the previous link properties had a global IPv6 address and an + // IPv6 default route then also consider the loss of that default route + // to be a loss of provisioning. See b/27962810. + if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { + delta = ProvisioningChange.LOST_PROVISIONING; + } + + return delta; + } + + private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) { + switch (delta) { + case GAINED_PROVISIONING: + if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); } + recordMetric(IpManagerEvent.PROVISIONING_OK); + mCallback.onProvisioningSuccess(newLp); + break; + + case LOST_PROVISIONING: + if (DBG) { Log.d(mTag, "onProvisioningFailure()"); } + recordMetric(IpManagerEvent.PROVISIONING_FAIL); + mCallback.onProvisioningFailure(newLp); + break; + + default: + if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); } + mCallback.onLinkPropertiesChange(newLp); + break; + } + } + + // Updates all IpClient-related state concerned with LinkProperties. + // Returns a ProvisioningChange for possibly notifying other interested + // parties that are not fronted by IpClient. + private ProvisioningChange setLinkProperties(LinkProperties newLp) { + if (mApfFilter != null) { + mApfFilter.setLinkProperties(newLp); + } + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.updateLinkProperties(newLp); + } + + ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp); + mLinkProperties = new LinkProperties(newLp); + + if (delta == ProvisioningChange.GAINED_PROVISIONING) { + // TODO: Add a proper ProvisionedState and cancel the alarm in + // its enter() method. + mProvisioningTimeoutAlarm.cancel(); + } + + return delta; + } + + private LinkProperties assembleLinkProperties() { + // [1] Create a new LinkProperties object to populate. + LinkProperties newLp = new LinkProperties(); + newLp.setInterfaceName(mInterfaceName); + + // [2] Pull in data from netlink: + // - IPv4 addresses + // - IPv6 addresses + // - IPv6 routes + // - IPv6 DNS servers + // + // N.B.: this is fundamentally race-prone and should be fixed by + // changing NetlinkTracker from a hybrid edge/level model to an + // edge-only model, or by giving IpClient its own netlink socket(s) + // so as to track all required information directly. + LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); + newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); + for (RouteInfo route : netlinkLinkProperties.getRoutes()) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); + + // [3] Add in data from DHCPv4, if available. + // + // mDhcpResults is never shared with any other owner so we don't have + // to worry about concurrent modification. + if (mDhcpResults != null) { + for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); + newLp.setDomains(mDhcpResults.domains); + + if (mDhcpResults.mtu != 0) { + newLp.setMtu(mDhcpResults.mtu); + } + } + + // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. + if (!TextUtils.isEmpty(mTcpBufferSizes)) { + newLp.setTcpBufferSizes(mTcpBufferSizes); + } + if (mHttpProxy != null) { + newLp.setHttpProxy(mHttpProxy); + } + + // [5] Add data from InitialConfiguration + if (mConfiguration != null && mConfiguration.mInitialConfig != null) { + InitialConfiguration config = mConfiguration.mInitialConfig; + // Add InitialConfiguration routes and dns server addresses once all addresses + // specified in the InitialConfiguration have been observed with Netlink. + if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { + for (IpPrefix prefix : config.directlyConnectedRoutes) { + newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); + } + } + addAllReachableDnsServers(newLp, config.dnsServers); + } + final LinkProperties oldLp = mLinkProperties; + if (DBG) { + Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", + netlinkLinkProperties, newLp, oldLp)); + } + + // TODO: also learn via netlink routes specified by an InitialConfiguration and specified + // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. + return newLp; + } + + private static void addAllReachableDnsServers( + LinkProperties lp, Iterable<InetAddress> dnses) { + // TODO: Investigate deleting this reachability check. We should be + // able to pass everything down to netd and let netd do evaluation + // and RFC6724-style sorting. + for (InetAddress dns : dnses) { + if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { + lp.addDnsServer(dns); + } + } + } + + // Returns false if we have lost provisioning, true otherwise. + private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { + final LinkProperties newLp = assembleLinkProperties(); + if (Objects.equals(newLp, mLinkProperties)) { + return true; + } + final ProvisioningChange delta = setLinkProperties(newLp); + if (sendCallbacks) { + dispatchCallback(delta, newLp); + } + return (delta != ProvisioningChange.LOST_PROVISIONING); + } + + private void handleIPv4Success(DhcpResults dhcpResults) { + mDhcpResults = new DhcpResults(dhcpResults); + final LinkProperties newLp = assembleLinkProperties(); + final ProvisioningChange delta = setLinkProperties(newLp); + + if (DBG) { + Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); + } + mCallback.onNewDhcpResults(dhcpResults); + dispatchCallback(delta, newLp); + } + + private void handleIPv4Failure() { + // TODO: Investigate deleting this clearIPv4Address() call. + // + // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances + // that could trigger a call to this function. If we missed handling + // that message in StartedState for some reason we would still clear + // any addresses upon entry to StoppedState. + mInterfaceCtrl.clearIPv4Address(); + mDhcpResults = null; + if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); } + mCallback.onNewDhcpResults(null); + + handleProvisioningFailure(); + } + + private void handleProvisioningFailure() { + final LinkProperties newLp = assembleLinkProperties(); + ProvisioningChange delta = setLinkProperties(newLp); + // If we've gotten here and we're still not provisioned treat that as + // a total loss of provisioning. + // + // Either (a) static IP configuration failed or (b) DHCPv4 failed AND + // there was no usable IPv6 obtained before a non-zero provisioning + // timeout expired. + // + // Regardless: GAME OVER. + if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) { + delta = ProvisioningChange.LOST_PROVISIONING; + } + + dispatchCallback(delta, newLp); + if (delta == ProvisioningChange.LOST_PROVISIONING) { + transitionTo(mStoppingState); + } + } + + private void doImmediateProvisioningFailure(int failureType) { + logError("onProvisioningFailure(): %s", failureType); + recordMetric(failureType); + mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); + } + + private boolean startIPv4() { + // If we have a StaticIpConfiguration attempt to apply it and + // handle the result accordingly. + if (mConfiguration.mStaticIpConfig != null) { + if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { + handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); + } else { + return false; + } + } else { + // Start DHCPv4. + mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceName); + mDhcpClient.registerForPreDhcpNotification(); + mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); + } + + return true; + } + + private boolean startIPv6() { + return mInterfaceCtrl.setIPv6PrivacyExtensions(true) && + mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) && + mInterfaceCtrl.enableIPv6(); + } + + private boolean applyInitialConfig(InitialConfiguration config) { + // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. + for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { + if (!mInterfaceCtrl.addAddress(addr)) return false; + } + + return true; + } + + private boolean startIpReachabilityMonitor() { + try { + mIpReachabilityMonitor = new IpReachabilityMonitor( + mContext, + mInterfaceName, + mLog, + new IpReachabilityMonitor.Callback() { + @Override + public void notifyLost(InetAddress ip, String logMsg) { + mCallback.onReachabilityLost(logMsg); + } + }, + mMultinetworkPolicyTracker); + } catch (IllegalArgumentException iae) { + // Failed to start IpReachabilityMonitor. Log it and call + // onProvisioningFailure() immediately. + // + // See http://b/31038971. + logError("IpReachabilityMonitor failure: %s", iae); + mIpReachabilityMonitor = null; + } + + return (mIpReachabilityMonitor != null); + } + + private void stopAllIP() { + // We don't need to worry about routes, just addresses, because: + // - disableIpv6() will clear autoconf IPv6 routes as well, and + // - we don't get IPv4 routes from netlink + // so we neither react to nor need to wait for changes in either. + + mInterfaceCtrl.disableIPv6(); + mInterfaceCtrl.clearAllAddresses(); + } + + class StoppedState extends State { + @Override + public void enter() { + stopAllIP(); + + resetLinkProperties(); + if (mStartTimeMillis > 0) { + recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); + mStartTimeMillis = 0; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_TERMINATE_AFTER_STOP: + stopStateMachineUpdaters(); + quit(); + break; + + case CMD_STOP: + break; + + case CMD_START: + mConfiguration = (ProvisioningConfiguration) msg.obj; + transitionTo(mStartedState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_SET_MULTICAST_FILTER: + mMulticastFiltering = (boolean) msg.obj; + break; + + case DhcpClient.CMD_ON_QUIT: + // Everything is already stopped. + logError("Unexpected CMD_ON_QUIT (already stopped)."); + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + class StoppingState extends State { + @Override + public void enter() { + if (mDhcpClient == null) { + // There's no DHCPv4 for which to wait; proceed to stopped. + transitionTo(mStoppedState); + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_STOP: + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_ON_QUIT: + mDhcpClient = null; + transitionTo(mStoppedState); + break; + + default: + deferMessage(msg); + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + class StartedState extends State { + @Override + public void enter() { + mStartTimeMillis = SystemClock.elapsedRealtime(); + + if (mConfiguration.mProvisioningTimeoutMs > 0) { + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mProvisioningTimeoutMs; + mProvisioningTimeoutAlarm.schedule(alarmTime); + } + + if (readyToProceed()) { + transitionTo(mRunningState); + } else { + // Clear all IPv4 and IPv6 before proceeding to RunningState. + // Clean up any leftover state from an abnormal exit from + // tethering or during an IpClient restart. + stopAllIP(); + } + } + + @Override + public void exit() { + mProvisioningTimeoutAlarm.cancel(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_STOP: + transitionTo(mStoppingState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + if (readyToProceed()) { + transitionTo(mRunningState); + } + break; + + case EVENT_PROVISIONING_TIMEOUT: + handleProvisioningFailure(); + break; + + default: + // It's safe to process messages out of order because the + // only message that can both + // a) be received at this time and + // b) affect provisioning state + // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). + deferMessage(msg); + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + + boolean readyToProceed() { + return (!mLinkProperties.hasIPv4Address() && + !mLinkProperties.hasGlobalIPv6Address()); + } + } + + class RunningState extends State { + private ConnectivityPacketTracker mPacketTracker; + private boolean mDhcpActionInFlight; + + @Override + public void enter() { + // Get the Configuration for ApfFilter from Context + final boolean filter802_3Frames = + mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); + + final int[] ethTypeBlackList = mContext.getResources().getIntArray( + R.array.config_apfEthTypeBlackList); + + mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface, + mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList); + // TODO: investigate the effects of any multicast filtering racing/interfering with the + // rest of this IP configuration startup. + if (mApfFilter == null) { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + + mPacketTracker = createPacketTracker(); + if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); + + if (mConfiguration.mEnableIPv6 && !startIPv6()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); + transitionTo(mStoppingState); + return; + } + + if (mConfiguration.mEnableIPv4 && !startIPv4()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); + transitionTo(mStoppingState); + return; + } + + final InitialConfiguration config = mConfiguration.mInitialConfig; + if ((config != null) && !applyInitialConfig(config)) { + // TODO introduce a new IpManagerEvent constant to distinguish this error case. + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + transitionTo(mStoppingState); + return; + } + + if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { + doImmediateProvisioningFailure( + IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); + transitionTo(mStoppingState); + return; + } + } + + @Override + public void exit() { + stopDhcpAction(); + + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.stop(); + mIpReachabilityMonitor = null; + } + + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); + mDhcpClient.doQuit(); + } + + if (mPacketTracker != null) { + mPacketTracker.stop(); + mPacketTracker = null; + } + + if (mApfFilter != null) { + mApfFilter.shutdown(); + mApfFilter = null; + } + + resetLinkProperties(); + } + + private ConnectivityPacketTracker createPacketTracker() { + try { + return new ConnectivityPacketTracker( + getHandler(), mNetworkInterface, mConnectivityPacketLog); + } catch (IllegalArgumentException e) { + return null; + } + } + + private void ensureDhcpAction() { + if (!mDhcpActionInFlight) { + mCallback.onPreDhcpAction(); + mDhcpActionInFlight = true; + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mRequestedPreDhcpActionMs; + mDhcpActionTimeoutAlarm.schedule(alarmTime); + } + } + + private void stopDhcpAction() { + mDhcpActionTimeoutAlarm.cancel(); + if (mDhcpActionInFlight) { + mCallback.onPostDhcpAction(); + mDhcpActionInFlight = false; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_STOP: + transitionTo(mStoppingState); + break; + + case CMD_START: + logError("ALERT: START received in StartedState. Please fix caller."); + break; + + case CMD_CONFIRM: + // TODO: Possibly introduce a second type of confirmation + // that both probes (a) on-link neighbors and (b) does + // a DHCPv4 RENEW. We used to do this on Wi-Fi framework + // roams. + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.probeAll(); + } + break; + + case EVENT_PRE_DHCP_ACTION_COMPLETE: + // It's possible to reach here if, for example, someone + // calls completedPreDhcpAction() after provisioning with + // a static IP configuration. + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { + transitionTo(mStoppingState); + } + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_SET_MULTICAST_FILTER: { + mMulticastFiltering = (boolean) msg.obj; + if (mApfFilter != null) { + mApfFilter.setMulticastFilter(mMulticastFiltering); + } else { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + break; + } + + case EVENT_DHCPACTION_TIMEOUT: + stopDhcpAction(); + break; + + case DhcpClient.CMD_PRE_DHCP_ACTION: + if (mConfiguration.mRequestedPreDhcpActionMs > 0) { + ensureDhcpAction(); + } else { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { + final LinkAddress ipAddress = (LinkAddress) msg.obj; + if (mInterfaceCtrl.setIPv4Address(ipAddress)) { + mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); + } else { + logError("Failed to set IPv4 address."); + dispatchCallback(ProvisioningChange.LOST_PROVISIONING, + new LinkProperties(mLinkProperties)); + transitionTo(mStoppingState); + } + break; + } + + // This message is only received when: + // + // a) initial address acquisition succeeds, + // b) renew succeeds or is NAK'd, + // c) rebind succeeds or is NAK'd, or + // c) the lease expires, + // + // but never when initial address acquisition fails. The latter + // condition is now governed by the provisioning timeout. + case DhcpClient.CMD_POST_DHCP_ACTION: + stopDhcpAction(); + + switch (msg.arg1) { + case DhcpClient.DHCP_SUCCESS: + handleIPv4Success((DhcpResults) msg.obj); + break; + case DhcpClient.DHCP_FAILURE: + handleIPv4Failure(); + break; + default: + logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); + } + break; + + case DhcpClient.CMD_ON_QUIT: + // DHCPv4 quit early for some reason. + logError("Unexpected CMD_ON_QUIT."); + mDhcpClient = null; + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + private static class MessageHandlingLogger { + public String processedInState; + public String receivedInState; + + public void reset() { + processedInState = null; + receivedInState = null; + } + + public void handled(State processedIn, IState receivedIn) { + processedInState = processedIn.getClass().getSimpleName(); + receivedInState = receivedIn.getName(); + } + + public String toString() { + return String.format("rcvd_in=%s, proc_in=%s", + receivedInState, processedInState); + } + } + + // TODO: extract out into CollectionUtils. + static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { + for (T t : coll) { + if (fn.test(t)) { + return true; + } + } + return false; + } + + static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { + return !any(coll, not(fn)); + } + + static <T> Predicate<T> not(Predicate<T> fn) { + return (t) -> !fn.test(t); + } + + static <T> String join(String delimiter, Collection<T> coll) { + return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); + } + + static <T> T find(Iterable<T> coll, Predicate<T> fn) { + for (T t: coll) { + if (fn.test(t)) { + return t; + } + } + return null; + } + + static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) { + return coll.stream().filter(fn).collect(Collectors.toList()); + } +} diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java index bc07b810..b12cb32c 100644 --- a/android/net/ip/IpManager.java +++ b/android/net/ip/IpManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,1708 +16,161 @@ package android.net.ip; -import com.android.internal.util.MessageUtils; -import com.android.internal.util.WakeupMessage; - import android.content.Context; -import android.net.DhcpResults; import android.net.INetd; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties.ProvisioningChange; import android.net.LinkProperties; -import android.net.ProxyInfo; -import android.net.RouteInfo; +import android.net.Network; import android.net.StaticIpConfiguration; import android.net.apf.ApfCapabilities; -import android.net.apf.ApfFilter; -import android.net.dhcp.DhcpClient; -import android.net.metrics.IpConnectivityLog; -import android.net.metrics.IpManagerEvent; -import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; -import android.net.util.NetworkConstants; -import android.net.util.SharedLog; import android.os.INetworkManagementService; -import android.os.Message; -import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.LocalLog; -import android.util.Log; -import android.util.SparseArray; +import android.net.apf.ApfCapabilities; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.R; -import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.IState; -import com.android.internal.util.Preconditions; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.server.net.NetlinkTracker; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * IpManager - * - * This class provides the interface to IP-layer provisioning and maintenance - * functionality that can be used by transport layers like Wi-Fi, Ethernet, - * et cetera. - * - * [ Lifetime ] - * IpManager is designed to be instantiated as soon as the interface name is - * known and can be as long-lived as the class containing it (i.e. declaring - * it "private final" is okay). +/* + * TODO: Delete this altogether in favor of its renamed successor: IpClient. * * @hide */ -public class IpManager extends StateMachine { - private static final boolean DBG = false; - - // For message logging. - private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class }; - private static final SparseArray<String> sWhatToString = - MessageUtils.findMessageNames(sMessageClasses); - - /** - * Callbacks for handling IpManager events. - */ - public static class Callback { - // In order to receive onPreDhcpAction(), call #withPreDhcpAction() - // when constructing a ProvisioningConfiguration. - // - // Implementations of onPreDhcpAction() must call - // IpManager#completedPreDhcpAction() to indicate that DHCP is clear - // to proceed. - public void onPreDhcpAction() {} - public void onPostDhcpAction() {} - - // This is purely advisory and not an indication of provisioning - // success or failure. This is only here for callers that want to - // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress). - // DHCPv4 or static IPv4 configuration failure or success can be - // determined by whether or not the passed-in DhcpResults object is - // null or not. - public void onNewDhcpResults(DhcpResults dhcpResults) {} - - public void onProvisioningSuccess(LinkProperties newLp) {} - public void onProvisioningFailure(LinkProperties newLp) {} - - // Invoked on LinkProperties changes. - public void onLinkPropertiesChange(LinkProperties newLp) {} - - // Called when the internal IpReachabilityMonitor (if enabled) has - // detected the loss of a critical number of required neighbors. - public void onReachabilityLost(String logMsg) {} - - // Called when the IpManager state machine terminates. - public void onQuit() {} - - // Install an APF program to filter incoming packets. - public void installPacketFilter(byte[] filter) {} - - // If multicast filtering cannot be accomplished with APF, this function will be called to - // actuate multicast filtering using another means. - public void setFallbackMulticastFilter(boolean enabled) {} - - // Enabled/disable Neighbor Discover offload functionality. This is - // called, for example, whenever 464xlat is being started or stopped. - public void setNeighborDiscoveryOffload(boolean enable) {} - } - - public static class WaitForProvisioningCallback extends Callback { - private LinkProperties mCallbackLinkProperties; - - public LinkProperties waitForProvisioning() { - synchronized (this) { - try { - wait(); - } catch (InterruptedException e) {} - return mCallbackLinkProperties; - } +public class IpManager extends IpClient { + public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration { + public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) { + super(ipcConfig); } - @Override - public void onProvisioningSuccess(LinkProperties newLp) { - synchronized (this) { - mCallbackLinkProperties = newLp; - notify(); - } - } - - @Override - public void onProvisioningFailure(LinkProperties newLp) { - synchronized (this) { - mCallbackLinkProperties = null; - notify(); - } - } - } - - // Use a wrapper class to log in order to ensure complete and detailed - // logging. This method is lighter weight than annotations/reflection - // and has the following benefits: - // - // - No invoked method can be forgotten. - // Any new method added to IpManager.Callback must be overridden - // here or it will never be called. - // - // - No invoking call site can be forgotten. - // Centralized logging in this way means call sites don't need to - // remember to log, and therefore no call site can be forgotten. - // - // - No variation in log format among call sites. - // Encourages logging of any available arguments, and all call sites - // are necessarily logged identically. - // - // TODO: Find an lighter weight approach. - private class LoggingCallbackWrapper extends Callback { - private static final String PREFIX = "INVOKE "; - private Callback mCallback; - - public LoggingCallbackWrapper(Callback callback) { - mCallback = callback; - } - - private void log(String msg) { - mLog.log(PREFIX + msg); - } - - @Override - public void onPreDhcpAction() { - mCallback.onPreDhcpAction(); - log("onPreDhcpAction()"); - } - @Override - public void onPostDhcpAction() { - mCallback.onPostDhcpAction(); - log("onPostDhcpAction()"); - } - @Override - public void onNewDhcpResults(DhcpResults dhcpResults) { - mCallback.onNewDhcpResults(dhcpResults); - log("onNewDhcpResults({" + dhcpResults + "})"); - } - @Override - public void onProvisioningSuccess(LinkProperties newLp) { - mCallback.onProvisioningSuccess(newLp); - log("onProvisioningSuccess({" + newLp + "})"); - } - @Override - public void onProvisioningFailure(LinkProperties newLp) { - mCallback.onProvisioningFailure(newLp); - log("onProvisioningFailure({" + newLp + "})"); - } - @Override - public void onLinkPropertiesChange(LinkProperties newLp) { - mCallback.onLinkPropertiesChange(newLp); - log("onLinkPropertiesChange({" + newLp + "})"); - } - @Override - public void onReachabilityLost(String logMsg) { - mCallback.onReachabilityLost(logMsg); - log("onReachabilityLost(" + logMsg + ")"); - } - @Override - public void onQuit() { - mCallback.onQuit(); - log("onQuit()"); - } - @Override - public void installPacketFilter(byte[] filter) { - mCallback.installPacketFilter(filter); - log("installPacketFilter(byte[" + filter.length + "])"); - } - @Override - public void setFallbackMulticastFilter(boolean enabled) { - mCallback.setFallbackMulticastFilter(enabled); - log("setFallbackMulticastFilter(" + enabled + ")"); - } - @Override - public void setNeighborDiscoveryOffload(boolean enable) { - mCallback.setNeighborDiscoveryOffload(enable); - log("setNeighborDiscoveryOffload(" + enable + ")"); - } - } - - /** - * This class encapsulates parameters to be passed to - * IpManager#startProvisioning(). A defensive copy is made by IpManager - * and the values specified herein are in force until IpManager#stop() - * is called. - * - * Example use: - * - * final ProvisioningConfiguration config = - * mIpManager.buildProvisioningConfiguration() - * .withPreDhcpAction() - * .withProvisioningTimeoutMs(36 * 1000) - * .build(); - * mIpManager.startProvisioning(config); - * ... - * mIpManager.stop(); - * - * The specified provisioning configuration will only be active until - * IpManager#stop() is called. Future calls to IpManager#startProvisioning() - * must specify the configuration again. - */ - public static class ProvisioningConfiguration { - // TODO: Delete this default timeout once those callers that care are - // fixed to pass in their preferred timeout. - // - // We pick 36 seconds so we can send DHCP requests at - // - // t=0, t=2, t=6, t=14, t=30 - // - // allowing for 10% jitter. - private static final int DEFAULT_TIMEOUT_MS = 36 * 1000; - - public static class Builder { - private ProvisioningConfiguration mConfig = new ProvisioningConfiguration(); - + public static class Builder extends IpClient.ProvisioningConfiguration.Builder { + @Override public Builder withoutIPv4() { - mConfig.mEnableIPv4 = false; + super.withoutIPv4(); return this; } - + @Override public Builder withoutIPv6() { - mConfig.mEnableIPv6 = false; + super.withoutIPv6(); return this; } - + @Override public Builder withoutIpReachabilityMonitor() { - mConfig.mUsingIpReachabilityMonitor = false; + super.withoutIpReachabilityMonitor(); return this; } - + @Override public Builder withPreDhcpAction() { - mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS; + super.withPreDhcpAction(); return this; } - + @Override public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { - mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs; + super.withPreDhcpAction(dhcpActionTimeoutMs); return this; } - + // No Override; locally defined type. public Builder withInitialConfiguration(InitialConfiguration initialConfig) { - mConfig.mInitialConfig = initialConfig; + super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig); return this; } - + @Override public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { - mConfig.mStaticIpConfig = staticConfig; + super.withStaticConfiguration(staticConfig); return this; } - + @Override public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { - mConfig.mApfCapabilities = apfCapabilities; + super.withApfCapabilities(apfCapabilities); return this; } - + @Override public Builder withProvisioningTimeoutMs(int timeoutMs) { - mConfig.mProvisioningTimeoutMs = timeoutMs; + super.withProvisioningTimeoutMs(timeoutMs); return this; } - + @Override public Builder withIPv6AddrGenModeEUI64() { - mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64; + super.withIPv6AddrGenModeEUI64(); return this; } - + @Override public Builder withIPv6AddrGenModeStablePrivacy() { - mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; + super.withIPv6AddrGenModeStablePrivacy(); return this; } - + @Override + public Builder withNetwork(Network network) { + super.withNetwork(network); + return this; + } + @Override + public Builder withDisplayName(String displayName) { + super.withDisplayName(displayName); + return this; + } + @Override public ProvisioningConfiguration build() { - return new ProvisioningConfiguration(mConfig); + return new ProvisioningConfiguration(super.build()); } } + } - /* package */ boolean mEnableIPv4 = true; - /* package */ boolean mEnableIPv6 = true; - /* package */ boolean mUsingIpReachabilityMonitor = true; - /* package */ int mRequestedPreDhcpActionMs; - /* package */ InitialConfiguration mInitialConfig; - /* package */ StaticIpConfiguration mStaticIpConfig; - /* package */ ApfCapabilities mApfCapabilities; - /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS; - /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; - - public ProvisioningConfiguration() {} // used by Builder - - public ProvisioningConfiguration(ProvisioningConfiguration other) { - mEnableIPv4 = other.mEnableIPv4; - mEnableIPv6 = other.mEnableIPv6; - mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor; - mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs; - mInitialConfig = InitialConfiguration.copy(other.mInitialConfig); - mStaticIpConfig = other.mStaticIpConfig; - mApfCapabilities = other.mApfCapabilities; - mProvisioningTimeoutMs = other.mProvisioningTimeoutMs; - } + public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { + return new ProvisioningConfiguration.Builder(); + } - @Override - public String toString() { - return new StringJoiner(", ", getClass().getSimpleName() + "{", "}") - .add("mEnableIPv4: " + mEnableIPv4) - .add("mEnableIPv6: " + mEnableIPv6) - .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor) - .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs) - .add("mInitialConfig: " + mInitialConfig) - .add("mStaticIpConfig: " + mStaticIpConfig) - .add("mApfCapabilities: " + mApfCapabilities) - .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs) - .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) - .toString(); - } + public static class InitialConfiguration extends IpClient.InitialConfiguration { + } - public boolean isValid() { - return (mInitialConfig == null) || mInitialConfig.isValid(); - } + public static class Callback extends IpClient.Callback { } - public static class InitialConfiguration { - public final Set<LinkAddress> ipAddresses = new HashSet<>(); - public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>(); - public final Set<InetAddress> dnsServers = new HashSet<>(); - public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config + public static class WaitForProvisioningCallback extends Callback { + private LinkProperties mCallbackLinkProperties; - public static InitialConfiguration copy(InitialConfiguration config) { - if (config == null) { - return null; + public LinkProperties waitForProvisioning() { + synchronized (this) { + try { + wait(); + } catch (InterruptedException e) {} + return mCallbackLinkProperties; } - InitialConfiguration configCopy = new InitialConfiguration(); - configCopy.ipAddresses.addAll(config.ipAddresses); - configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes); - configCopy.dnsServers.addAll(config.dnsServers); - return configCopy; } @Override - public String toString() { - return String.format( - "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)", - join(", ", ipAddresses), join(", ", directlyConnectedRoutes), - join(", ", dnsServers), gateway); - } - - public boolean isValid() { - if (ipAddresses.isEmpty()) { - return false; - } - - // For every IP address, there must be at least one prefix containing that address. - for (LinkAddress addr : ipAddresses) { - if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) { - return false; - } - } - // For every dns server, there must be at least one prefix containing that address. - for (InetAddress addr : dnsServers) { - if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) { - return false; - } - } - // All IPv6 LinkAddresses have an RFC7421-suitable prefix length - // (read: compliant with RFC4291#section2.5.4). - if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) { - return false; - } - // If directlyConnectedRoutes contains an IPv6 default route - // then ipAddresses MUST contain at least one non-ULA GUA. - if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute) - && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) { - return false; - } - // The prefix length of routes in directlyConnectedRoutes be within reasonable - // bounds for IPv6: /48-/64 just as we’d accept in RIOs. - if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) { - return false; - } - // There no more than one IPv4 address - if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) { - return false; + public void onProvisioningSuccess(LinkProperties newLp) { + synchronized (this) { + mCallbackLinkProperties = newLp; + notify(); } - - return true; } - /** - * @return true if the given list of addressess and routes satisfies provisioning for this - * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality - * because addresses and routes seen by Netlink will contain additional fields like flags, - * interfaces, and so on. If this InitialConfiguration has no IP address specified, the - * provisioning check always fails. - * - * If the given list of routes is null, only addresses are taken into considerations. - */ - public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) { - if (ipAddresses.isEmpty()) { - return false; - } - - for (LinkAddress addr : ipAddresses) { - if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) { - return false; - } - } - - if (routes != null) { - for (IpPrefix prefix : directlyConnectedRoutes) { - if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) { - return false; - } - } + @Override + public void onProvisioningFailure(LinkProperties newLp) { + synchronized (this) { + mCallbackLinkProperties = null; + notify(); } - - return true; - } - - private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) { - return !route.hasGateway() && prefix.equals(route.getDestination()); - } - - private static boolean isPrefixLengthCompliant(LinkAddress addr) { - return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength()); - } - - private static boolean isPrefixLengthCompliant(IpPrefix prefix) { - return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); - } - - private static boolean isCompliantIPv6PrefixLength(int prefixLength) { - return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength) - && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH); - } - - private static boolean isIPv6DefaultRoute(IpPrefix prefix) { - return prefix.getAddress().equals(Inet6Address.ANY); - } - - private static boolean isIPv6GUA(LinkAddress addr) { - return addr.isIPv6() && addr.isGlobalPreferred(); } } - public static final String DUMP_ARG = "ipmanager"; - public static final String DUMP_ARG_CONFIRM = "confirm"; - - private static final int CMD_TERMINATE_AFTER_STOP = 1; - private static final int CMD_STOP = 2; - private static final int CMD_START = 3; - private static final int CMD_CONFIRM = 4; - private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; - // Sent by NetlinkTracker to communicate netlink events. - private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; - private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; - private static final int CMD_UPDATE_HTTP_PROXY = 8; - private static final int CMD_SET_MULTICAST_FILTER = 9; - private static final int EVENT_PROVISIONING_TIMEOUT = 10; - private static final int EVENT_DHCPACTION_TIMEOUT = 11; - - private static final int MAX_LOG_RECORDS = 500; - private static final int MAX_PACKET_RECORDS = 100; - - private static final boolean NO_CALLBACKS = false; - private static final boolean SEND_CALLBACKS = true; - - // This must match the interface prefix in clatd.c. - // TODO: Revert this hack once IpManager and Nat464Xlat work in concert. - private static final String CLAT_PREFIX = "v4-"; - - private final State mStoppedState = new StoppedState(); - private final State mStoppingState = new StoppingState(); - private final State mStartedState = new StartedState(); - private final State mRunningState = new RunningState(); - - private final String mTag; - private final Context mContext; - private final String mInterfaceName; - private final String mClatInterfaceName; - @VisibleForTesting - protected final Callback mCallback; - private final INetworkManagementService mNwService; - private final NetlinkTracker mNetlinkTracker; - private final WakeupMessage mProvisioningTimeoutAlarm; - private final WakeupMessage mDhcpActionTimeoutAlarm; - private final MultinetworkPolicyTracker mMultinetworkPolicyTracker; - private final SharedLog mLog; - private final LocalLog mConnectivityPacketLog; - private final MessageHandlingLogger mMsgStateLogger; - private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); - private final InterfaceController mInterfaceCtrl; - - private NetworkInterface mNetworkInterface; - - /** - * Non-final member variables accessed only from within our StateMachine. - */ - private LinkProperties mLinkProperties; - private ProvisioningConfiguration mConfiguration; - private IpReachabilityMonitor mIpReachabilityMonitor; - private DhcpClient mDhcpClient; - private DhcpResults mDhcpResults; - private String mTcpBufferSizes; - private ProxyInfo mHttpProxy; - private ApfFilter mApfFilter; - private boolean mMulticastFiltering; - private long mStartTimeMillis; - public IpManager(Context context, String ifName, Callback callback) { this(context, ifName, callback, INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)), NetdService.getInstance()); } - /** - * An expanded constructor, useful for dependency injection. - * TODO: migrate all test users to mock IpManager directly and remove this ctor. - */ public IpManager(Context context, String ifName, Callback callback, INetworkManagementService nwService) { this(context, ifName, callback, nwService, NetdService.getInstance()); } @VisibleForTesting - IpManager(Context context, String ifName, Callback callback, + public IpManager(Context context, String ifName, Callback callback, INetworkManagementService nwService, INetd netd) { - super(IpManager.class.getSimpleName() + "." + ifName); - mTag = getName(); - - mContext = context; - mInterfaceName = ifName; - mClatInterfaceName = CLAT_PREFIX + ifName; - mCallback = new LoggingCallbackWrapper(callback); - mNwService = nwService; - - mLog = new SharedLog(MAX_LOG_RECORDS, mTag); - mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS); - mMsgStateLogger = new MessageHandlingLogger(); - - mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog); - - mNetlinkTracker = new NetlinkTracker( - mInterfaceName, - new NetlinkTracker.Callback() { - @Override - public void update() { - sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); - } - }) { - @Override - public void interfaceAdded(String iface) { - super.interfaceAdded(iface); - if (mClatInterfaceName.equals(iface)) { - mCallback.setNeighborDiscoveryOffload(false); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceAdded(" + iface +")"; - logMsg(msg); - } - - @Override - public void interfaceRemoved(String iface) { - super.interfaceRemoved(iface); - // TODO: Also observe mInterfaceName going down and take some - // kind of appropriate action. - if (mClatInterfaceName.equals(iface)) { - // TODO: consider sending a message to the IpManager main - // StateMachine thread, in case "NDO enabled" state becomes - // tied to more things that 464xlat operation. - mCallback.setNeighborDiscoveryOffload(true); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceRemoved(" + iface +")"; - logMsg(msg); - } - - private void logMsg(String msg) { - Log.d(mTag, msg); - getHandler().post(() -> { mLog.log("OBSERVED " + msg); }); - } - }; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - - mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(), - () -> { mLog.log("OBSERVED AvoidBadWifi changed"); }); - - mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); - mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); - - // Anything the StateMachine may access must have been instantiated - // before this point. - configureAndStartStateMachine(); - - // Anything that may send messages to the StateMachine must only be - // configured to do so after the StateMachine has started (above). - startStateMachineUpdaters(); - } - - private void configureAndStartStateMachine() { - addState(mStoppedState); - addState(mStartedState); - addState(mRunningState, mStartedState); - addState(mStoppingState); - - setInitialState(mStoppedState); - - super.start(); - } - - private void startStateMachineUpdaters() { - try { - mNwService.registerObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't register NetlinkTracker: %s", e); - } - - mMultinetworkPolicyTracker.start(); - } - - private void stopStateMachineUpdaters() { - try { - mNwService.unregisterObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't unregister NetlinkTracker: %s", e); - } - - mMultinetworkPolicyTracker.shutdown(); - } - - @Override - protected void onQuitting() { - mCallback.onQuit(); - } - - // Shut down this IpManager instance altogether. - public void shutdown() { - stop(); - sendMessage(CMD_TERMINATE_AFTER_STOP); - } - - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); + super(context, ifName, callback, nwService, netd); } public void startProvisioning(ProvisioningConfiguration req) { - if (!req.isValid()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - return; - } - - getNetworkInterface(); - - mCallback.setNeighborDiscoveryOffload(true); - sendMessage(CMD_START, new ProvisioningConfiguration(req)); - } - - // TODO: Delete this. - public void startProvisioning(StaticIpConfiguration staticIpConfig) { - startProvisioning(buildProvisioningConfiguration() - .withStaticConfiguration(staticIpConfig) - .build()); - } - - public void startProvisioning() { - startProvisioning(new ProvisioningConfiguration()); - } - - public void stop() { - sendMessage(CMD_STOP); - } - - public void confirmConfiguration() { - sendMessage(CMD_CONFIRM); - } - - public void completedPreDhcpAction() { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - - /** - * Set the TCP buffer sizes to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setTcpBufferSizes(String tcpBufferSizes) { - sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); - } - - /** - * Set the HTTP Proxy configuration to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setHttpProxy(ProxyInfo proxyInfo) { - sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); - } - - /** - * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, - * if not, Callback.setFallbackMulticastFilter() is called. - */ - public void setMulticastFilter(boolean enabled) { - sendMessage(CMD_SET_MULTICAST_FILTER, enabled); - } - - public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { - // Execute confirmConfiguration() and take no further action. - confirmConfiguration(); - return; - } - - // Thread-unsafe access to mApfFilter but just used for debugging. - final ApfFilter apfFilter = mApfFilter; - final ProvisioningConfiguration provisioningConfig = mConfiguration; - final ApfCapabilities apfCapabilities = (provisioningConfig != null) - ? provisioningConfig.mApfCapabilities : null; - - IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println(mTag + " APF dump:"); - pw.increaseIndent(); - if (apfFilter != null) { - apfFilter.dump(pw); - } else { - pw.print("No active ApfFilter; "); - if (provisioningConfig == null) { - pw.println("IpManager not yet started."); - } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { - pw.println("Hardware does not support APF."); - } else { - pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); - } - } - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " current ProvisioningConfiguration:"); - pw.increaseIndent(); - pw.println(Objects.toString(provisioningConfig, "N/A")); - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " StateMachine dump:"); - pw.increaseIndent(); - mLog.dump(fd, pw, args); - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " connectivity packet log:"); - pw.println(); - pw.println("Debug with python and scapy via:"); - pw.println("shell$ python"); - pw.println(">>> from scapy import all as scapy"); - pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); - pw.println(); - - pw.increaseIndent(); - mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); - pw.decreaseIndent(); - } - - - /** - * Internals. - */ - - @Override - protected String getWhatToString(int what) { - return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); - } - - @Override - protected String getLogRecString(Message msg) { - final String logLine = String.format( - "%s/%d %d %d %s [%s]", - mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(), - msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); - - final String richerLogLine = getWhatToString(msg.what) + " " + logLine; - mLog.log(richerLogLine); - if (DBG) { - Log.d(mTag, richerLogLine); - } - - mMsgStateLogger.reset(); - return logLine; - } - - @Override - protected boolean recordLogRec(Message msg) { - // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, - // and we already log any LinkProperties change that results in an - // invocation of IpManager.Callback#onLinkPropertiesChange(). - final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); - if (!shouldLog) { - mMsgStateLogger.reset(); - } - return shouldLog; - } - - private void logError(String fmt, Object... args) { - final String msg = "ERROR " + String.format(fmt, args); - Log.e(mTag, msg); - mLog.log(msg); - } - - private void getNetworkInterface() { - try { - mNetworkInterface = NetworkInterface.getByName(mInterfaceName); - } catch (SocketException | NullPointerException e) { - // TODO: throw new IllegalStateException. - logError("Failed to get interface object: %s", e); - } - } - - // This needs to be called with care to ensure that our LinkProperties - // are in sync with the actual LinkProperties of the interface. For example, - // we should only call this if we know for sure that there are no IP addresses - // assigned to the interface, etc. - private void resetLinkProperties() { - mNetlinkTracker.clearLinkProperties(); - mConfiguration = null; - mDhcpResults = null; - mTcpBufferSizes = ""; - mHttpProxy = null; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - } - - private void recordMetric(final int type) { - if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); } - final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis; - mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); - } - - // For now: use WifiStateMachine's historical notion of provisioned. - @VisibleForTesting - static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) { - // For historical reasons, we should connect even if all we have is - // an IPv4 address and nothing else. - if (lp.hasIPv4Address() || lp.isProvisioned()) { - return true; - } - if (config == null) { - return false; - } - - // When an InitialConfiguration is specified, ignore any difference with previous - // properties and instead check if properties observed match the desired properties. - return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); - } - - // TODO: Investigate folding all this into the existing static function - // LinkProperties.compareProvisioning() or some other single function that - // takes two LinkProperties objects and returns a ProvisioningChange - // object that is a correct and complete assessment of what changed, taking - // account of the asymmetries described in the comments in this function. - // Then switch to using it everywhere (IpReachabilityMonitor, etc.). - private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { - ProvisioningChange delta; - InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; - final boolean wasProvisioned = isProvisioned(oldLp, config); - final boolean isProvisioned = isProvisioned(newLp, config); - - if (!wasProvisioned && isProvisioned) { - delta = ProvisioningChange.GAINED_PROVISIONING; - } else if (wasProvisioned && isProvisioned) { - delta = ProvisioningChange.STILL_PROVISIONED; - } else if (!wasProvisioned && !isProvisioned) { - delta = ProvisioningChange.STILL_NOT_PROVISIONED; - } else { - // (wasProvisioned && !isProvisioned) - // - // Note that this is true even if we lose a configuration element - // (e.g., a default gateway) that would not be required to advance - // into provisioned state. This is intended: if we have a default - // router and we lose it, that's a sure sign of a problem, but if - // we connect to a network with no IPv4 DNS servers, we consider - // that to be a network without DNS servers and connect anyway. - // - // See the comment below. - delta = ProvisioningChange.LOST_PROVISIONING; - } - - final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); - final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address(); - final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute(); - - // If bad wifi avoidance is disabled, then ignore IPv6 loss of - // provisioning. Otherwise, when a hotspot that loses Internet - // access sends out a 0-lifetime RA to its clients, the clients - // will disconnect and then reconnect, avoiding the bad hotspot, - // instead of getting stuck on the bad hotspot. http://b/31827713 . - // - // This is incorrect because if the hotspot then regains Internet - // access with a different prefix, TCP connections on the - // deprecated addresses will remain stuck. - // - // Note that we can still be disconnected by IpReachabilityMonitor - // if the IPv6 default gateway (but not the IPv6 DNS servers; see - // accompanying code in IpReachabilityMonitor) is unreachable. - final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi(); - - // Additionally: - // - // Partial configurations (e.g., only an IPv4 address with no DNS - // servers and no default route) are accepted as long as DHCPv4 - // succeeds. On such a network, isProvisioned() will always return - // false, because the configuration is not complete, but we want to - // connect anyway. It might be a disconnected network such as a - // Chromecast or a wireless printer, for example. - // - // Because on such a network isProvisioned() will always return false, - // delta will never be LOST_PROVISIONING. So check for loss of - // provisioning here too. - if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { - delta = ProvisioningChange.LOST_PROVISIONING; - } - - // Additionally: - // - // If the previous link properties had a global IPv6 address and an - // IPv6 default route then also consider the loss of that default route - // to be a loss of provisioning. See b/27962810. - if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { - delta = ProvisioningChange.LOST_PROVISIONING; - } - - return delta; - } - - private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) { - switch (delta) { - case GAINED_PROVISIONING: - if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); } - recordMetric(IpManagerEvent.PROVISIONING_OK); - mCallback.onProvisioningSuccess(newLp); - break; - - case LOST_PROVISIONING: - if (DBG) { Log.d(mTag, "onProvisioningFailure()"); } - recordMetric(IpManagerEvent.PROVISIONING_FAIL); - mCallback.onProvisioningFailure(newLp); - break; - - default: - if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); } - mCallback.onLinkPropertiesChange(newLp); - break; - } - } - - // Updates all IpManager-related state concerned with LinkProperties. - // Returns a ProvisioningChange for possibly notifying other interested - // parties that are not fronted by IpManager. - private ProvisioningChange setLinkProperties(LinkProperties newLp) { - if (mApfFilter != null) { - mApfFilter.setLinkProperties(newLp); - } - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.updateLinkProperties(newLp); - } - - ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp); - mLinkProperties = new LinkProperties(newLp); - - if (delta == ProvisioningChange.GAINED_PROVISIONING) { - // TODO: Add a proper ProvisionedState and cancel the alarm in - // its enter() method. - mProvisioningTimeoutAlarm.cancel(); - } - - return delta; - } - - private LinkProperties assembleLinkProperties() { - // [1] Create a new LinkProperties object to populate. - LinkProperties newLp = new LinkProperties(); - newLp.setInterfaceName(mInterfaceName); - - // [2] Pull in data from netlink: - // - IPv4 addresses - // - IPv6 addresses - // - IPv6 routes - // - IPv6 DNS servers - // - // N.B.: this is fundamentally race-prone and should be fixed by - // changing NetlinkTracker from a hybrid edge/level model to an - // edge-only model, or by giving IpManager its own netlink socket(s) - // so as to track all required information directly. - LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); - newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); - for (RouteInfo route : netlinkLinkProperties.getRoutes()) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); - - // [3] Add in data from DHCPv4, if available. - // - // mDhcpResults is never shared with any other owner so we don't have - // to worry about concurrent modification. - if (mDhcpResults != null) { - for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); - newLp.setDomains(mDhcpResults.domains); - - if (mDhcpResults.mtu != 0) { - newLp.setMtu(mDhcpResults.mtu); - } - } - - // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. - if (!TextUtils.isEmpty(mTcpBufferSizes)) { - newLp.setTcpBufferSizes(mTcpBufferSizes); - } - if (mHttpProxy != null) { - newLp.setHttpProxy(mHttpProxy); - } - - // [5] Add data from InitialConfiguration - if (mConfiguration != null && mConfiguration.mInitialConfig != null) { - InitialConfiguration config = mConfiguration.mInitialConfig; - // Add InitialConfiguration routes and dns server addresses once all addresses - // specified in the InitialConfiguration have been observed with Netlink. - if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { - for (IpPrefix prefix : config.directlyConnectedRoutes) { - newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); - } - } - addAllReachableDnsServers(newLp, config.dnsServers); - } - final LinkProperties oldLp = mLinkProperties; - if (DBG) { - Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", - netlinkLinkProperties, newLp, oldLp)); - } - - // TODO: also learn via netlink routes specified by an InitialConfiguration and specified - // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. - return newLp; - } - - private static void addAllReachableDnsServers( - LinkProperties lp, Iterable<InetAddress> dnses) { - // TODO: Investigate deleting this reachability check. We should be - // able to pass everything down to netd and let netd do evaluation - // and RFC6724-style sorting. - for (InetAddress dns : dnses) { - if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { - lp.addDnsServer(dns); - } - } - } - - // Returns false if we have lost provisioning, true otherwise. - private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { - final LinkProperties newLp = assembleLinkProperties(); - if (Objects.equals(newLp, mLinkProperties)) { - return true; - } - final ProvisioningChange delta = setLinkProperties(newLp); - if (sendCallbacks) { - dispatchCallback(delta, newLp); - } - return (delta != ProvisioningChange.LOST_PROVISIONING); - } - - private void handleIPv4Success(DhcpResults dhcpResults) { - mDhcpResults = new DhcpResults(dhcpResults); - final LinkProperties newLp = assembleLinkProperties(); - final ProvisioningChange delta = setLinkProperties(newLp); - - if (DBG) { - Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); - } - mCallback.onNewDhcpResults(dhcpResults); - dispatchCallback(delta, newLp); - } - - private void handleIPv4Failure() { - // TODO: Investigate deleting this clearIPv4Address() call. - // - // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances - // that could trigger a call to this function. If we missed handling - // that message in StartedState for some reason we would still clear - // any addresses upon entry to StoppedState. - mInterfaceCtrl.clearIPv4Address(); - mDhcpResults = null; - if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); } - mCallback.onNewDhcpResults(null); - - handleProvisioningFailure(); - } - - private void handleProvisioningFailure() { - final LinkProperties newLp = assembleLinkProperties(); - ProvisioningChange delta = setLinkProperties(newLp); - // If we've gotten here and we're still not provisioned treat that as - // a total loss of provisioning. - // - // Either (a) static IP configuration failed or (b) DHCPv4 failed AND - // there was no usable IPv6 obtained before a non-zero provisioning - // timeout expired. - // - // Regardless: GAME OVER. - if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) { - delta = ProvisioningChange.LOST_PROVISIONING; - } - - dispatchCallback(delta, newLp); - if (delta == ProvisioningChange.LOST_PROVISIONING) { - transitionTo(mStoppingState); - } - } - - private void doImmediateProvisioningFailure(int failureType) { - logError("onProvisioningFailure(): %s", failureType); - recordMetric(failureType); - mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); - } - - private boolean startIPv4() { - // If we have a StaticIpConfiguration attempt to apply it and - // handle the result accordingly. - if (mConfiguration.mStaticIpConfig != null) { - if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { - handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); - } else { - return false; - } - } else { - // Start DHCPv4. - mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName); - mDhcpClient.registerForPreDhcpNotification(); - mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); - } - - return true; - } - - private boolean startIPv6() { - return mInterfaceCtrl.setIPv6PrivacyExtensions(true) && - mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) && - mInterfaceCtrl.enableIPv6(); - } - - private boolean applyInitialConfig(InitialConfiguration config) { - // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. - for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { - if (!mInterfaceCtrl.addAddress(addr)) return false; - } - - return true; - } - - private boolean startIpReachabilityMonitor() { - try { - mIpReachabilityMonitor = new IpReachabilityMonitor( - mContext, - mInterfaceName, - mLog, - new IpReachabilityMonitor.Callback() { - @Override - public void notifyLost(InetAddress ip, String logMsg) { - mCallback.onReachabilityLost(logMsg); - } - }, - mMultinetworkPolicyTracker); - } catch (IllegalArgumentException iae) { - // Failed to start IpReachabilityMonitor. Log it and call - // onProvisioningFailure() immediately. - // - // See http://b/31038971. - logError("IpReachabilityMonitor failure: %s", iae); - mIpReachabilityMonitor = null; - } - - return (mIpReachabilityMonitor != null); - } - - private void stopAllIP() { - // We don't need to worry about routes, just addresses, because: - // - disableIpv6() will clear autoconf IPv6 routes as well, and - // - we don't get IPv4 routes from netlink - // so we neither react to nor need to wait for changes in either. - - mInterfaceCtrl.disableIPv6(); - mInterfaceCtrl.clearAllAddresses(); - } - - class StoppedState extends State { - @Override - public void enter() { - stopAllIP(); - - resetLinkProperties(); - if (mStartTimeMillis > 0) { - recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); - mStartTimeMillis = 0; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_TERMINATE_AFTER_STOP: - stopStateMachineUpdaters(); - quit(); - break; - - case CMD_STOP: - break; - - case CMD_START: - mConfiguration = (ProvisioningConfiguration) msg.obj; - transitionTo(mStartedState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: - mMulticastFiltering = (boolean) msg.obj; - break; - - case DhcpClient.CMD_ON_QUIT: - // Everything is already stopped. - logError("Unexpected CMD_ON_QUIT (already stopped)."); - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StoppingState extends State { - @Override - public void enter() { - if (mDhcpClient == null) { - // There's no DHCPv4 for which to wait; proceed to stopped. - transitionTo(mStoppedState); - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_STOP: - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_ON_QUIT: - mDhcpClient = null; - transitionTo(mStoppedState); - break; - - default: - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StartedState extends State { - @Override - public void enter() { - mStartTimeMillis = SystemClock.elapsedRealtime(); - - if (mConfiguration.mProvisioningTimeoutMs > 0) { - final long alarmTime = SystemClock.elapsedRealtime() + - mConfiguration.mProvisioningTimeoutMs; - mProvisioningTimeoutAlarm.schedule(alarmTime); - } - - if (readyToProceed()) { - transitionTo(mRunningState); - } else { - // Clear all IPv4 and IPv6 before proceeding to RunningState. - // Clean up any leftover state from an abnormal exit from - // tethering or during an IpManager restart. - stopAllIP(); - } - } - - @Override - public void exit() { - mProvisioningTimeoutAlarm.cancel(); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - if (readyToProceed()) { - transitionTo(mRunningState); - } - break; - - case EVENT_PROVISIONING_TIMEOUT: - handleProvisioningFailure(); - break; - - default: - // It's safe to process messages out of order because the - // only message that can both - // a) be received at this time and - // b) affect provisioning state - // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - - boolean readyToProceed() { - return (!mLinkProperties.hasIPv4Address() && - !mLinkProperties.hasGlobalIPv6Address()); - } - } - - class RunningState extends State { - private ConnectivityPacketTracker mPacketTracker; - private boolean mDhcpActionInFlight; - - @Override - public void enter() { - // Get the Configuration for ApfFilter from Context - boolean filter802_3Frames = - mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); - - int[] ethTypeBlackList = mContext.getResources().getIntArray( - R.array.config_apfEthTypeBlackList); - - mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface, - mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList); - // TODO: investigate the effects of any multicast filtering racing/interfering with the - // rest of this IP configuration startup. - if (mApfFilter == null) { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - - mPacketTracker = createPacketTracker(); - if (mPacketTracker != null) mPacketTracker.start(); - - if (mConfiguration.mEnableIPv6 && !startIPv6()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); - transitionTo(mStoppingState); - return; - } - - if (mConfiguration.mEnableIPv4 && !startIPv4()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); - transitionTo(mStoppingState); - return; - } - - InitialConfiguration config = mConfiguration.mInitialConfig; - if ((config != null) && !applyInitialConfig(config)) { - // TODO introduce a new IpManagerEvent constant to distinguish this error case. - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - transitionTo(mStoppingState); - return; - } - - if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { - doImmediateProvisioningFailure( - IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); - transitionTo(mStoppingState); - return; - } - } - - @Override - public void exit() { - stopDhcpAction(); - - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.stop(); - mIpReachabilityMonitor = null; - } - - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); - mDhcpClient.doQuit(); - } - - if (mPacketTracker != null) { - mPacketTracker.stop(); - mPacketTracker = null; - } - - if (mApfFilter != null) { - mApfFilter.shutdown(); - mApfFilter = null; - } - - resetLinkProperties(); - } - - private ConnectivityPacketTracker createPacketTracker() { - try { - return new ConnectivityPacketTracker( - getHandler(), mNetworkInterface, mConnectivityPacketLog); - } catch (IllegalArgumentException e) { - return null; - } - } - - private void ensureDhcpAction() { - if (!mDhcpActionInFlight) { - mCallback.onPreDhcpAction(); - mDhcpActionInFlight = true; - final long alarmTime = SystemClock.elapsedRealtime() + - mConfiguration.mRequestedPreDhcpActionMs; - mDhcpActionTimeoutAlarm.schedule(alarmTime); - } - } - - private void stopDhcpAction() { - mDhcpActionTimeoutAlarm.cancel(); - if (mDhcpActionInFlight) { - mCallback.onPostDhcpAction(); - mDhcpActionInFlight = false; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case CMD_START: - logError("ALERT: START received in StartedState. Please fix caller."); - break; - - case CMD_CONFIRM: - // TODO: Possibly introduce a second type of confirmation - // that both probes (a) on-link neighbors and (b) does - // a DHCPv4 RENEW. We used to do this on Wi-Fi framework - // roams. - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.probeAll(); - } - break; - - case EVENT_PRE_DHCP_ACTION_COMPLETE: - // It's possible to reach here if, for example, someone - // calls completedPreDhcpAction() after provisioning with - // a static IP configuration. - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { - transitionTo(mStoppingState); - } - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: { - mMulticastFiltering = (boolean) msg.obj; - if (mApfFilter != null) { - mApfFilter.setMulticastFilter(mMulticastFiltering); - } else { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - break; - } - - case EVENT_DHCPACTION_TIMEOUT: - stopDhcpAction(); - break; - - case DhcpClient.CMD_PRE_DHCP_ACTION: - if (mConfiguration.mRequestedPreDhcpActionMs > 0) { - ensureDhcpAction(); - } else { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { - final LinkAddress ipAddress = (LinkAddress) msg.obj; - if (mInterfaceCtrl.setIPv4Address(ipAddress)) { - mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); - } else { - logError("Failed to set IPv4 address."); - dispatchCallback(ProvisioningChange.LOST_PROVISIONING, - new LinkProperties(mLinkProperties)); - transitionTo(mStoppingState); - } - break; - } - - // This message is only received when: - // - // a) initial address acquisition succeeds, - // b) renew succeeds or is NAK'd, - // c) rebind succeeds or is NAK'd, or - // c) the lease expires, - // - // but never when initial address acquisition fails. The latter - // condition is now governed by the provisioning timeout. - case DhcpClient.CMD_POST_DHCP_ACTION: - stopDhcpAction(); - - switch (msg.arg1) { - case DhcpClient.DHCP_SUCCESS: - handleIPv4Success((DhcpResults) msg.obj); - break; - case DhcpClient.DHCP_FAILURE: - handleIPv4Failure(); - break; - default: - logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); - } - break; - - case DhcpClient.CMD_ON_QUIT: - // DHCPv4 quit early for some reason. - logError("Unexpected CMD_ON_QUIT."); - mDhcpClient = null; - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - private static class MessageHandlingLogger { - public String processedInState; - public String receivedInState; - - public void reset() { - processedInState = null; - receivedInState = null; - } - - public void handled(State processedIn, IState receivedIn) { - processedInState = processedIn.getClass().getSimpleName(); - receivedInState = receivedIn.getName(); - } - - public String toString() { - return String.format("rcvd_in=%s, proc_in=%s", - receivedInState, processedInState); - } - } - - // TODO: extract out into CollectionUtils. - static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { - for (T t : coll) { - if (fn.test(t)) { - return true; - } - } - return false; - } - - static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { - return !any(coll, not(fn)); - } - - static <T> Predicate<T> not(Predicate<T> fn) { - return (t) -> !fn.test(t); - } - - static <T> String join(String delimiter, Collection<T> coll) { - return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); - } - - static <T> T find(Iterable<T> coll, Predicate<T> fn) { - for (T t: coll) { - if (fn.test(t)) { - return t; - } - } - return null; - } - - static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) { - return coll.stream().filter(fn).collect(Collectors.toList()); + super.startProvisioning((IpClient.ProvisioningConfiguration) req); } } diff --git a/android/net/metrics/ConnectStats.java b/android/net/metrics/ConnectStats.java index 30b26562..2495cab1 100644 --- a/android/net/metrics/ConnectStats.java +++ b/android/net/metrics/ConnectStats.java @@ -20,6 +20,7 @@ import android.net.NetworkCapabilities; import android.system.OsConstants; import android.util.IntArray; import android.util.SparseIntArray; + import com.android.internal.util.BitUtils; import com.android.internal.util.TokenBucket; @@ -43,6 +44,8 @@ public class ConnectStats { public final TokenBucket mLatencyTb; /** Maximum number of latency values recorded. */ public final int mMaxLatencyRecords; + /** Total count of events */ + public int eventCount = 0; /** Total count of successful connects. */ public int connectCount = 0; /** Total count of successful connects done in blocking mode. */ @@ -57,12 +60,15 @@ public class ConnectStats { mMaxLatencyRecords = maxLatencyRecords; } - public void addEvent(int errno, int latencyMs, String ipAddr) { + boolean addEvent(int errno, int latencyMs, String ipAddr) { + eventCount++; if (isSuccess(errno)) { countConnect(errno, ipAddr); countLatency(errno, latencyMs); + return true; } else { countError(errno); + return false; } } @@ -101,7 +107,7 @@ public class ConnectStats { return (errno == 0) || isNonBlocking(errno); } - private static boolean isNonBlocking(int errno) { + static boolean isNonBlocking(int errno) { // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS. // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY. return (errno == EINPROGRESS) || (errno == EALREADY); @@ -117,6 +123,7 @@ public class ConnectStats { for (int t : BitUtils.unpackBits(transports)) { builder.append(NetworkCapabilities.transportNameOf(t)).append(", "); } + builder.append(String.format("%d events, ", eventCount)); builder.append(String.format("%d success, ", connectCount)); builder.append(String.format("%d blocking, ", connectBlockingCount)); builder.append(String.format("%d IPv6 dst", ipv6ConnectCount)); diff --git a/android/net/metrics/DnsEvent.java b/android/net/metrics/DnsEvent.java index a4970e4d..81b098bb 100644 --- a/android/net/metrics/DnsEvent.java +++ b/android/net/metrics/DnsEvent.java @@ -17,11 +17,13 @@ package android.net.metrics; import android.net.NetworkCapabilities; -import java.util.Arrays; + import com.android.internal.util.BitUtils; +import java.util.Arrays; + /** - * A DNS event recorded by NetdEventListenerService. + * A batch of DNS events recorded by NetdEventListenerService for a specific network. * {@hide} */ final public class DnsEvent { @@ -38,6 +40,8 @@ final public class DnsEvent { // the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event // is spread across the three array at position i. public int eventCount; + // The number of successful DNS queries recorded. + public int successCount; // The types of DNS queries as defined in INetdEventListener. public byte[] eventTypes; // Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there @@ -54,10 +58,11 @@ final public class DnsEvent { latenciesMs = new int[initialCapacity]; } - public void addResult(byte eventType, byte returnCode, int latencyMs) { + boolean addResult(byte eventType, byte returnCode, int latencyMs) { + boolean isSuccess = (returnCode == 0); if (eventCount >= SIZE_LIMIT) { // TODO: implement better rate limiting that does not biases metrics. - return; + return isSuccess; } if (eventCount == eventTypes.length) { resize((int) (1.4 * eventCount)); @@ -66,6 +71,10 @@ final public class DnsEvent { returnCodes[eventCount] = returnCode; latenciesMs[eventCount] = latencyMs; eventCount++; + if (isSuccess) { + successCount++; + } + return isSuccess; } public void resize(int newLength) { @@ -80,6 +89,8 @@ final public class DnsEvent { for (int t : BitUtils.unpackBits(transports)) { builder.append(NetworkCapabilities.transportNameOf(t)).append(", "); } - return builder.append(eventCount).append(" events)").toString(); + builder.append(String.format("%d events, ", eventCount)); + builder.append(String.format("%d success)", successCount)); + return builder.toString(); } } diff --git a/android/net/metrics/NetworkMetrics.java b/android/net/metrics/NetworkMetrics.java new file mode 100644 index 00000000..2b662a0c --- /dev/null +++ b/android/net/metrics/NetworkMetrics.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.metrics; + +import android.net.NetworkCapabilities; + +import com.android.internal.util.BitUtils; +import com.android.internal.util.TokenBucket; + +import java.util.StringJoiner; + +/** + * A class accumulating network metrics received from Netd regarding dns queries and + * connect() calls on a given network. + * + * This class also accumulates running sums of dns and connect latency stats and + * error counts for bug report logging. + * + * @hide + */ +public class NetworkMetrics { + + private static final int INITIAL_DNS_BATCH_SIZE = 100; + private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000; + + // The network id of the Android Network. + public final int netId; + // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java. + public final long transports; + // Accumulated metrics for connect events. + public final ConnectStats connectMetrics; + // Accumulated metrics for dns events. + public final DnsEvent dnsMetrics; + // Running sums of latencies and error counts for connect and dns events. + public final Summary summary; + // Running sums of the most recent latencies and error counts for connect and dns events. + // Starts null until some events are accumulated. + // Allows to collect periodic snapshot of the running summaries for a given network. + public Summary pendingSummary; + + public NetworkMetrics(int netId, long transports, TokenBucket tb) { + this.netId = netId; + this.transports = transports; + this.connectMetrics = + new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS); + this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE); + this.summary = new Summary(netId, transports); + } + + /** + * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them + * into the long running Summary statistics of this NetworkMetrics, and also clear them. + */ + public Summary getPendingStats() { + Summary s = pendingSummary; + pendingSummary = null; + if (s != null) { + summary.merge(s); + } + return s; + } + + /** Accumulate a dns query result reported by netd. */ + public void addDnsResult(int eventType, int returnCode, int latencyMs) { + if (pendingSummary == null) { + pendingSummary = new Summary(netId, transports); + } + boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs); + pendingSummary.dnsLatencies.count(latencyMs); + pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1); + } + + /** Accumulate a connect query result reported by netd. */ + public void addConnectResult(int error, int latencyMs, String ipAddr) { + if (pendingSummary == null) { + pendingSummary = new Summary(netId, transports); + } + boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr); + pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1); + if (ConnectStats.isNonBlocking(error)) { + pendingSummary.connectLatencies.count(latencyMs); + } + } + + /** Represents running sums for dns and connect average error counts and average latencies. */ + public static class Summary { + + public final int netId; + public final long transports; + // DNS latencies measured in milliseconds. + public final Metrics dnsLatencies = new Metrics(); + // DNS error rate measured in percentage points. + public final Metrics dnsErrorRate = new Metrics(); + // Blocking connect latencies measured in milliseconds. + public final Metrics connectLatencies = new Metrics(); + // Blocking and non blocking connect error rate measured in percentage points. + public final Metrics connectErrorRate = new Metrics(); + + public Summary(int netId, long transports) { + this.netId = netId; + this.transports = transports; + } + + void merge(Summary that) { + dnsLatencies.merge(that.dnsLatencies); + dnsErrorRate.merge(that.dnsErrorRate); + connectLatencies.merge(that.connectLatencies); + connectErrorRate.merge(that.connectErrorRate); + } + + @Override + public String toString() { + StringJoiner j = new StringJoiner(", ", "{", "}"); + j.add("netId=" + netId); + for (int t : BitUtils.unpackBits(transports)) { + j.add(NetworkCapabilities.transportNameOf(t)); + } + j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d", + (int) dnsLatencies.average(), (int) dnsLatencies.max, + 100 * dnsErrorRate.average(), dnsErrorRate.count)); + j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d", + (int) connectLatencies.average(), (int) connectLatencies.max, + 100 * connectErrorRate.average(), connectErrorRate.count)); + return j.toString(); + } + } + + /** Tracks a running sum and returns the average of a metric. */ + static class Metrics { + public double sum; + public double max = Double.MIN_VALUE; + public int count; + + void merge(Metrics that) { + this.count += that.count; + this.sum += that.sum; + this.max = Math.max(this.max, that.max); + } + + void count(double value) { + count++; + sum += value; + max = Math.max(max, value); + } + + double average() { + double a = sum / (double) count; + if (Double.isNaN(a)) { + a = 0; + } + return a; + } + } +} diff --git a/android/net/netlink/NetlinkSocket.java b/android/net/netlink/NetlinkSocket.java index a9e0cd99..f5f211d8 100644 --- a/android/net/netlink/NetlinkSocket.java +++ b/android/net/netlink/NetlinkSocket.java @@ -96,7 +96,7 @@ public class NetlinkSocket implements Closeable { mDescriptor = Os.socket( OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto); - Libcore.os.setsockoptInt( + Os.setsockoptInt( mDescriptor, OsConstants.SOL_SOCKET, OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE); } diff --git a/android/net/util/SharedLog.java b/android/net/util/SharedLog.java index 343d237f..bbd3d13e 100644 --- a/android/net/util/SharedLog.java +++ b/android/net/util/SharedLog.java @@ -106,6 +106,10 @@ public class SharedLog { record(Category.NONE, msg); } + public void logf(String fmt, Object... args) { + log(String.format(fmt, args)); + } + public void mark(String msg) { record(Category.MARK, msg); } diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java index b08b4b7c..649b0cef 100644 --- a/android/net/wifi/WifiManager.java +++ b/android/net/wifi/WifiManager.java @@ -1029,6 +1029,26 @@ public class WifiManager { } /** + * Return all matching WifiConfigurations for this ScanResult. + * + * An empty list will be returned when no configurations are installed or if no configurations + * match the ScanResult. + * + * @param scanResult scanResult that represents the BSSID + * @return A list of {@link WifiConfiguration} + * @throws UnsupportedOperationException if Passpoint is not enabled on the device. + * @hide + */ + public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) { + try { + return mService.getAllMatchingWifiConfigs(scanResult); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP. * * An empty list will be returned if no match is found. diff --git a/android/net/wifi/rtt/RangingRequest.java b/android/net/wifi/rtt/RangingRequest.java index 997b6800..a396281f 100644 --- a/android/net/wifi/rtt/RangingRequest.java +++ b/android/net/wifi/rtt/RangingRequest.java @@ -17,13 +17,22 @@ package android.net.wifi.rtt; import android.net.wifi.ScanResult; +import android.net.wifi.aware.AttachCallback; +import android.net.wifi.aware.DiscoverySessionCallback; +import android.net.wifi.aware.IdentityChangedListener; +import android.net.wifi.aware.PeerHandle; +import android.net.wifi.aware.WifiAwareManager; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import libcore.util.HexEncoding; + import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.StringJoiner; /** @@ -33,8 +42,8 @@ import java.util.StringJoiner; * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. * <p> * The ranging request is a batch request - specifying a set of devices (specified using - * {@link RangingRequest.Builder#addAp(ScanResult)} and - * {@link RangingRequest.Builder#addAps(List)}). + * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and + * {@link RangingRequest.Builder#addAccessPoints(List)}). * * @hide RTT_API */ @@ -44,8 +53,8 @@ public final class RangingRequest implements Parcelable { /** * Returns the maximum number of peers to range which can be specified in a single {@code * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g. - * through {@link RangingRequest.Builder#addAp(ScanResult)} or - * {@link RangingRequest.Builder#addAps(List)}. + * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or + * {@link RangingRequest.Builder#addAccessPoints(List)}. * * @return Maximum number of peers. */ @@ -94,11 +103,34 @@ public final class RangingRequest implements Parcelable { } /** @hide */ - public void enforceValidity() { + public void enforceValidity(boolean awareSupported) { if (mRttPeers.size() > MAX_PEERS) { throw new IllegalArgumentException( "Ranging to too many peers requested. Use getMaxPeers() API to get limit."); } + + for (RttPeer peer: mRttPeers) { + if (peer instanceof RttPeerAp) { + RttPeerAp apPeer = (RttPeerAp) peer; + if (apPeer.scanResult == null || apPeer.scanResult.BSSID == null) { + throw new IllegalArgumentException("Invalid AP peer specification"); + } + } else if (peer instanceof RttPeerAware) { + if (!awareSupported) { + throw new IllegalArgumentException( + "Request contains Aware peers - but Aware isn't supported on this " + + "device"); + } + + RttPeerAware awarePeer = (RttPeerAware) peer; + if (awarePeer.peerMacAddress == null && awarePeer.peerHandle == null) { + throw new IllegalArgumentException("Invalid Aware peer specification"); + } + } else { + throw new IllegalArgumentException( + "Request contains unknown peer specification types"); + } + } } /** @@ -116,7 +148,7 @@ public final class RangingRequest implements Parcelable { * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ - public Builder addAp(ScanResult apInfo) { + public Builder addAccessPoint(ScanResult apInfo) { if (apInfo == null) { throw new IllegalArgumentException("Null ScanResult!"); } @@ -133,17 +165,55 @@ public final class RangingRequest implements Parcelable { * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ - public Builder addAps(List<ScanResult> apInfos) { + public Builder addAccessPoints(List<ScanResult> apInfos) { if (apInfos == null) { throw new IllegalArgumentException("Null list of ScanResults!"); } for (ScanResult scanResult : apInfos) { - addAp(scanResult); + addAccessPoint(scanResult); } return this; } /** + * Add the device specified by the {@code peerMacAddress} to the list of devices with + * which to measure range. + * + * The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi + * Aware device may obtain its MAC address using the {@link IdentityChangedListener} + * provided to + * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}. + * + * * Note: in order to use this API the device must support Wi-Fi Aware + * {@link android.net.wifi.aware}. + * + * @param peerMacAddress The MAC address of the Wi-Fi Aware peer. + * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. + */ + public Builder addWifiAwarePeer(byte[] peerMacAddress) { + mRttPeers.add(new RttPeerAware(peerMacAddress)); + return this; + } + + /** + * Add a device specified by a {@link PeerHandle} to the list of devices with which to + * measure range. + * + * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g. + * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}. + * + * Note: in order to use this API the device must support Wi-Fi Aware + * {@link android.net.wifi.aware}. + * + * @param peerHandle The peer handler of the peer Wi-Fi Aware device. + * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. + */ + public Builder addWifiAwarePeer(PeerHandle peerHandle) { + mRttPeers.add(new RttPeerAware(peerHandle)); + return this; + } + + /** * Build {@link RangingRequest} given the current configurations made on the * builder. */ @@ -234,4 +304,89 @@ public final class RangingRequest implements Parcelable { return scanResult.hashCode(); } } -}
\ No newline at end of file + + /** @hide */ + public static class RttPeerAware implements RttPeer, Parcelable { + public PeerHandle peerHandle; + public byte[] peerMacAddress; + + public RttPeerAware(PeerHandle peerHandle) { + if (peerHandle == null) { + throw new IllegalArgumentException("Null peerHandle"); + } + this.peerHandle = peerHandle; + peerMacAddress = null; + } + + public RttPeerAware(byte[] peerMacAddress) { + if (peerMacAddress == null) { + throw new IllegalArgumentException("Null peerMacAddress"); + } + + this.peerMacAddress = peerMacAddress; + peerHandle = null; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (peerHandle == null) { + dest.writeBoolean(false); + dest.writeByteArray(peerMacAddress); + } else { + dest.writeBoolean(true); + dest.writeInt(peerHandle.peerId); + } + } + + public static final Creator<RttPeerAware> CREATOR = new Creator<RttPeerAware>() { + @Override + public RttPeerAware[] newArray(int size) { + return new RttPeerAware[size]; + } + + @Override + public RttPeerAware createFromParcel(Parcel in) { + boolean peerHandleAvail = in.readBoolean(); + if (peerHandleAvail) { + return new RttPeerAware(new PeerHandle(in.readInt())); + } else { + return new RttPeerAware(in.createByteArray()); + } + } + }; + + @Override + public String toString() { + return new StringBuilder("RttPeerAware: peerHandle=").append( + peerHandle == null ? "<null>" : Integer.toString(peerHandle.peerId)).append( + ", peerMacAddress=").append(peerMacAddress == null ? "<null>" + : new String(HexEncoding.encode(peerMacAddress))).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof RttPeerAware)) { + return false; + } + + RttPeerAware lhs = (RttPeerAware) o; + + return Objects.equals(peerHandle, lhs.peerHandle) && Arrays.equals(peerMacAddress, + lhs.peerMacAddress); + } + + @Override + public int hashCode() { + return Objects.hash(peerHandle.peerId, peerMacAddress); + } + } +} diff --git a/android/net/wifi/rtt/RangingResult.java b/android/net/wifi/rtt/RangingResult.java index 918803ef..93e52aeb 100644 --- a/android/net/wifi/rtt/RangingResult.java +++ b/android/net/wifi/rtt/RangingResult.java @@ -16,13 +16,16 @@ package android.net.wifi.rtt; +import android.annotation.IntDef; +import android.net.wifi.aware.PeerHandle; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; import libcore.util.HexEncoding; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -40,28 +43,61 @@ import java.util.Objects; public final class RangingResult implements Parcelable { private static final String TAG = "RangingResult"; + /** @hide */ + @IntDef({STATUS_SUCCESS, STATUS_FAIL}) + @Retention(RetentionPolicy.SOURCE) + public @interface RangeResultStatus { + } + + /** + * Individual range request status, {@link #getStatus()}. Indicates ranging operation was + * successful and distance value is valid. + */ + public static final int STATUS_SUCCESS = 0; + + /** + * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed + * and the distance value is invalid. + */ + public static final int STATUS_FAIL = 1; + private final int mStatus; private final byte[] mMac; - private final int mDistanceCm; - private final int mDistanceStdDevCm; + private final PeerHandle mPeerHandle; + private final int mDistanceMm; + private final int mDistanceStdDevMm; private final int mRssi; private final long mTimestamp; /** @hide */ - public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi, - long timestamp) { + public RangingResult(@RangeResultStatus int status, byte[] mac, int distanceMm, + int distanceStdDevMm, int rssi, long timestamp) { mStatus = status; mMac = mac; - mDistanceCm = distanceCm; - mDistanceStdDevCm = distanceStdDevCm; + mPeerHandle = null; + mDistanceMm = distanceMm; + mDistanceStdDevMm = distanceStdDevMm; + mRssi = rssi; + mTimestamp = timestamp; + } + + /** @hide */ + public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm, + int distanceStdDevMm, int rssi, long timestamp) { + mStatus = status; + mMac = null; + mPeerHandle = peerHandle; + mDistanceMm = distanceMm; + mDistanceStdDevMm = distanceStdDevMm; mRssi = rssi; mTimestamp = timestamp; } /** - * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in - * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure. + * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and + * {@link #STATUS_FAIL} in case of failure. */ + @RangeResultStatus public int getStatus() { return mStatus; } @@ -70,57 +106,80 @@ public final class RangingResult implements Parcelable { * @return The MAC address of the device whose range measurement was requested. Will correspond * to the MAC address of the device in the {@link RangingRequest}. * <p> - * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL. + * Will return a {@code null} for results corresponding to requests issued using a {@code + * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API. */ public byte[] getMacAddress() { return mMac; } /** - * @return The distance (in cm) to the device specified by {@link #getMacAddress()}. + * @return The PeerHandle of the device whose reange measurement was requested. Will correspond + * to the PeerHandle of the devices requested using + * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}. * <p> - * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + * Will return a {@code null} for results corresponding to requests issued using a MAC address. */ - public int getDistanceCm() { - if (mStatus != RangingResultCallback.STATUS_SUCCESS) { - Log.e(TAG, "getDistanceCm(): invalid value retrieved"); + public PeerHandle getPeerHandle() { + return mPeerHandle; + } + + /** + * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or + * {@link #getPeerHandle()}. + * <p> + * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an + * exception. + */ + public int getDistanceMm() { + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus); } - return mDistanceCm; + return mDistanceMm; } /** - * @return The standard deviation of the measured distance (in cm) to the device specified by - * {@link #getMacAddress()}. The standard deviation is calculated over the measurements - * executed in a single RTT burst. + * @return The standard deviation of the measured distance (in mm) to the device specified by + * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated + * over the measurements executed in a single RTT burst. * <p> - * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an + * exception. */ - public int getDistanceStdDevCm() { - if (mStatus != RangingResultCallback.STATUS_SUCCESS) { - Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved"); + public int getDistanceStdDevMm() { + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus); } - return mDistanceStdDevCm; + return mDistanceStdDevMm; } /** * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement. * <p> - * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an + * exception. */ public int getRssi() { - if (mStatus != RangingResultCallback.STATUS_SUCCESS) { - // TODO: should this be an exception? - Log.e(TAG, "getRssi(): invalid value retrieved"); + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getRssi(): invoked on an invalid result: getStatus()=" + mStatus); } return mRssi; } /** - * @return The timestamp (in us) at which the ranging operation was performed + * @return The timestamp, in us since boot, at which the ranging operation was performed. * <p> - * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}. + * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an + * exception. */ - public long getRangingTimestamp() { + public long getRangingTimestampUs() { + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getRangingTimestamp(): invoked on an invalid result: getStatus()=" + mStatus); + } return mTimestamp; } @@ -135,8 +194,14 @@ public final class RangingResult implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mStatus); dest.writeByteArray(mMac); - dest.writeInt(mDistanceCm); - dest.writeInt(mDistanceStdDevCm); + if (mPeerHandle == null) { + dest.writeBoolean(false); + } else { + dest.writeBoolean(true); + dest.writeInt(mPeerHandle.peerId); + } + dest.writeInt(mDistanceMm); + dest.writeInt(mDistanceStdDevMm); dest.writeInt(mRssi); dest.writeLong(mTimestamp); } @@ -152,11 +217,22 @@ public final class RangingResult implements Parcelable { public RangingResult createFromParcel(Parcel in) { int status = in.readInt(); byte[] mac = in.createByteArray(); - int distanceCm = in.readInt(); - int distanceStdDevCm = in.readInt(); + boolean peerHandlePresent = in.readBoolean(); + PeerHandle peerHandle = null; + if (peerHandlePresent) { + peerHandle = new PeerHandle(in.readInt()); + } + int distanceMm = in.readInt(); + int distanceStdDevMm = in.readInt(); int rssi = in.readInt(); long timestamp = in.readLong(); - return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp); + if (peerHandlePresent) { + return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi, + timestamp); + } else { + return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi, + timestamp); + } } }; @@ -164,9 +240,10 @@ public final class RangingResult implements Parcelable { @Override public String toString() { return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append( - mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append( - ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append( - mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append( + mMac == null ? "<null>" : new String(HexEncoding.encodeToString(mMac))).append( + ", peerHandle=").append(mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append( + ", distanceMm=").append(mDistanceMm).append(", distanceStdDevMm=").append( + mDistanceStdDevMm).append(", rssi=").append(mRssi).append(", timestamp=").append( mTimestamp).append("]").toString(); } @@ -182,13 +259,15 @@ public final class RangingResult implements Parcelable { RangingResult lhs = (RangingResult) o; - return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) - && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm - && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp; + return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) && Objects.equals( + mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm + && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi + && mTimestamp == lhs.mTimestamp; } @Override public int hashCode() { - return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp); + return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi, + mTimestamp); } -}
\ No newline at end of file +} diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java index d7270ad2..7405e82e 100644 --- a/android/net/wifi/rtt/RangingResultCallback.java +++ b/android/net/wifi/rtt/RangingResultCallback.java @@ -16,35 +16,43 @@ package android.net.wifi.rtt; +import android.annotation.IntDef; import android.os.Handler; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** * Base class for ranging result callbacks. Should be extended by applications and set when calling - * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single - * result from a range request will be called in this object. + * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. If the + * ranging operation fails in whole (not attempted) then {@link #onRangingFailure(int)} will be + * called with a failure code. If the ranging operation is performed for each of the requested + * peers then the {@link #onRangingResults(List)} will be called with the set of results (@link + * {@link RangingResult}, each of which has its own success/failure code + * {@link RangingResult#getStatus()}. * * @hide RTT_API */ public abstract class RangingResultCallback { - /** - * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging - * operation was successful and distance value is valid. - */ - public static final int STATUS_SUCCESS = 0; + /** @hide */ + @IntDef({STATUS_CODE_FAIL}) + @Retention(RetentionPolicy.SOURCE) + public @interface RangingOperationStatus { + } /** - * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging - * operation failed and the distance value is invalid. + * A failure code for the whole ranging request operation. Indicates a failure. */ - public static final int STATUS_FAIL = 1; + public static final int STATUS_CODE_FAIL = 1; /** * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the * devices specified in the request was attempted. + * + * @param code A status code indicating the type of failure. */ - public abstract void onRangingFailure(); + public abstract void onRangingFailure(@RangingOperationStatus int code); /** * Called when a ranging operation was executed. The list of results corresponds to devices diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java index a085de17..435bb377 100644 --- a/android/net/wifi/rtt/WifiRttManager.java +++ b/android/net/wifi/rtt/WifiRttManager.java @@ -83,17 +83,18 @@ public class WifiRttManager { } @Override - public void onRangingResults(int status, List<RangingResult> results) throws RemoteException { - if (VDBG) { - Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results=" - + results); - } + public void onRangingFailure(int status) throws RemoteException { + if (VDBG) Log.v(TAG, "RttCallbackProxy: onRangingFailure: status=" + status); mHandler.post(() -> { - if (status == RangingResultCallback.STATUS_SUCCESS) { - mCallback.onRangingResults(results); - } else { - mCallback.onRangingFailure(); - } + mCallback.onRangingFailure(status); + }); + } + + @Override + public void onRangingResults(List<RangingResult> results) throws RemoteException { + if (VDBG) Log.v(TAG, "RttCallbackProxy: onRanginResults: results=" + results); + mHandler.post(() -> { + mCallback.onRangingResults(results); }); } } |