blob: c716fdfec074605b5d1e93b165b5ba4e4c232b53 [file] [log] [blame]
/*
* Copyright (C) 2015 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 static android.net.metrics.IpReachabilityEvent.NUD_FAILED;
import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
import static android.net.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.LinkProperties;
import android.net.RouteInfo;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpReachabilityEvent;
import android.net.networkstack.aidl.ip.ReachabilityLossReason;
import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.os.SystemClock;
import android.stats.connectivity.IpType;
import android.stats.connectivity.NudEventType;
import android.stats.connectivity.NudNeighborType;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.netlink.StructNdMsg;
import com.android.networkstack.R;
import com.android.networkstack.metrics.IpReachabilityMonitorMetrics;
import java.io.PrintWriter;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* IpReachabilityMonitor.
*
* Monitors on-link IP reachability and notifies callers whenever any on-link
* addresses of interest appear to have become unresponsive.
*
* This code does not concern itself with "why" a neighbour might have become
* unreachable. Instead, it primarily reacts to the kernel's notion of IP
* reachability for each of the neighbours we know to be critically important
* to normal network connectivity. As such, it is often "just the messenger":
* the neighbours about which it warns are already deemed by the kernel to have
* become unreachable.
*
*
* How it works:
*
* 1. The "on-link neighbours of interest" found in a given LinkProperties
* instance are added to a "watch list" via #updateLinkProperties().
* This usually means all default gateways and any on-link DNS servers.
*
* 2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,
* RTM_DELNEIGH), watching only for neighbours in the watch list.
*
* - A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and
* even NUD_PROBE is perfectly normal; we merely record the new state.
*
* - A neighbour's entry may be deleted (RTM_DELNEIGH), for example due
* to garbage collection. This is not necessarily of immediate
* concern; we record the neighbour as moving to NUD_NONE.
*
* - A neighbour transitioning to NUD_FAILED (for any reason) is
* critically important and is handled as described below in #4.
*
* 3. All on-link neighbours in the watch list can be forcibly "probed" by
* calling #probeAll(). This should be called whenever it is important to
* verify that critical neighbours on the link are still reachable, e.g.
* when roaming between BSSIDs.
*
* - The kernel will send unicast ARP requests for IPv4 neighbours and
* unicast NS packets for IPv6 neighbours. The expected replies will
* likely be unicast.
*
* - The forced probing is done holding a wakelock. The kernel may,
* however, initiate probing of a neighbor on its own, i.e. whenever
* a neighbour has expired from NUD_DELAY.
*
* - The kernel sends:
*
* /proc/sys/net/ipv{4,6}/neigh/<ifname>/ucast_solicit
*
* number of probes (usually 3) every:
*
* /proc/sys/net/ipv{4,6}/neigh/<ifname>/retrans_time_ms
*
* number of milliseconds (usually 1000ms). This normally results in
* 3 unicast packets, 1 per second.
*
* - If no response is received to any of the probe packets, the kernel
* marks the neighbour as being in state NUD_FAILED, and the listening
* process in #2 will learn of it.
*
* 4. We call the supplied Callback#notifyLost() function if the loss of a
* neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to
* become incomplete (a loss of provisioning).
*
* - For example, losing all our IPv4 on-link DNS servers (or losing
* our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6)
* provisioning; Callback#notifyLost() would be called.
*
* - Since it can be non-trivial to reacquire certain IP provisioning
* state it may be best for the link to disconnect completely and
* reconnect afresh.
*
* Accessing an instance of this class from multiple threads is NOT safe.
*
* @hide
*/
public class IpReachabilityMonitor {
private static final String TAG = "IpReachabilityMonitor";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
// Upper and lower bound for NUD probe parameters.
protected static final int MAX_NUD_SOLICIT_NUM = 15;
protected static final int MIN_NUD_SOLICIT_NUM = 5;
protected static final int MAX_NUD_SOLICIT_INTERVAL_MS = 1000;
protected static final int MIN_NUD_SOLICIT_INTERVAL_MS = 750;
protected static final int NUD_MCAST_RESOLICIT_NUM = 3;
private static final int INVALID_NUD_MCAST_RESOLICIT_NUM = -1;
private static final int INVALID_LEGACY_NUD_FAILURE_TYPE = -1;
public static final int INVALID_REACHABILITY_LOSS_TYPE = -1;
public interface Callback {
/**
* This callback function must execute as quickly as possible as it is
* run on the same thread that listens to kernel neighbor updates.
*
* TODO: refactor to something like notifyProvisioningLost(String msg).
*/
void notifyLost(InetAddress ip, String logMsg, NudEventType type);
}
/**
* Encapsulates IpReachabilityMonitor dependencies on systems that hinder unit testing.
* TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
*/
interface Dependencies {
void acquireWakeLock(long durationMs);
IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb);
boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled);
IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics();
static Dependencies makeDefault(Context context, String iface) {
final String lockName = TAG + "." + iface;
final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
return new Dependencies() {
public void acquireWakeLock(long durationMs) {
lock.acquire(durationMs);
}
public IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log,
NeighborEventConsumer cb) {
return new IpNeighborMonitor(h, log, cb);
}
public boolean isFeatureEnabled(final Context context, final String name,
boolean defaultEnabled) {
return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
defaultEnabled);
}
public IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics() {
return new IpReachabilityMonitorMetrics();
}
};
}
}
private final InterfaceParams mInterfaceParams;
private final IpNeighborMonitor mIpNeighborMonitor;
private final SharedLog mLog;
private final Dependencies mDependencies;
private final boolean mUsingMultinetworkPolicyTracker;
private final ConnectivityManager mCm;
private final IpConnectivityLog mMetricsLog;
private final Context mContext;
private final INetd mNetd;
private final IpReachabilityMonitorMetrics mIpReachabilityMetrics;
private LinkProperties mLinkProperties = new LinkProperties();
private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
// Time in milliseconds of the last forced probe request.
private volatile long mLastProbeTimeMs;
// Time in milliseconds of the last forced probe request due to roam or CMD_CONFIRM.
private long mLastProbeDueToRoamMs;
private long mLastProbeDueToConfirmMs;
private int mNumSolicits;
private int mInterSolicitIntervalMs;
@NonNull
private final Callback mCallback;
public IpReachabilityMonitor(
Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
boolean usingMultinetworkPolicyTracker, Dependencies dependencies, final INetd netd) {
this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker, dependencies,
new IpConnectivityLog(), netd);
}
@VisibleForTesting
IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log,
Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies,
final IpConnectivityLog metricsLog, final INetd netd) {
if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
mContext = context;
mInterfaceParams = ifParams;
mLog = log.forSubComponent(TAG);
mCallback = callback;
mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
mCm = context.getSystemService(ConnectivityManager.class);
mDependencies = dependencies;
mMetricsLog = metricsLog;
mNetd = netd;
Preconditions.checkNotNull(mNetd);
Preconditions.checkArgument(!TextUtils.isEmpty(mInterfaceParams.name));
// In case the overylaid parameters specify an invalid configuration, set the parameters
// to the hardcoded defaults first, then set them to the values used in the steady state.
try {
int numResolicits = isMulticastResolicitEnabled()
? NUD_MCAST_RESOLICIT_NUM
: INVALID_NUD_MCAST_RESOLICIT_NUM;
setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
} catch (Exception e) {
Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
}
setNeighbourParametersForSteadyState();
mIpNeighborMonitor = mDependencies.makeIpNeighborMonitor(h, mLog,
(NeighborEvent event) -> {
if (mInterfaceParams.index != event.ifindex) return;
if (!mNeighborWatchList.containsKey(event.ip)) return;
final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
// TODO: Consider what to do with other states that are not within
// NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
if (event.nudState == StructNdMsg.NUD_FAILED) {
// After both unicast probe and multicast probe(if mcast_resolicit is not 0)
// attempts fail, trigger the neighbor lost event and disconnect.
mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
handleNeighborLost(event);
} else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
handleNeighborReachable(prev, event);
}
});
mIpNeighborMonitor.start();
mIpReachabilityMetrics = dependencies.getIpReachabilityMonitorMetrics();
}
public void stop() {
mIpNeighborMonitor.stop();
clearLinkProperties();
}
public void dump(PrintWriter pw) {
if (Looper.myLooper() == mIpNeighborMonitor.getHandler().getLooper()) {
pw.println(describeWatchList("\n"));
return;
}
final ConditionVariable cv = new ConditionVariable(false);
mIpNeighborMonitor.getHandler().post(() -> {
pw.println(describeWatchList("\n"));
cv.open();
});
if (!cv.block(1000)) {
pw.println("Timed out waiting for IpReachabilityMonitor dump");
}
}
private String describeWatchList() { return describeWatchList(" "); }
private String describeWatchList(String sep) {
final StringBuilder sb = new StringBuilder();
sb.append("iface{" + mInterfaceParams + "}," + sep);
sb.append("ntable=[" + sep);
String delimiter = "";
for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
delimiter = "," + sep;
}
sb.append("]");
return sb.toString();
}
private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
for (RouteInfo route : routes) {
if (!route.hasGateway() && route.matches(ip)) {
return true;
}
}
return false;
}
private boolean hasDefaultRouterNeighborMacAddressChanged(
@Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) {
if (prev == null || !isNeighborDefaultRouter(event)) return false;
return !event.macAddr.equals(prev.macAddr);
}
private boolean isNeighborDefaultRouter(@NonNull final NeighborEvent event) {
// For the IPv6 link-local scoped address, equals() works because the NeighborEvent.ip
// doesn't have a scope id and Inet6Address#equals doesn't consider scope id neither.
for (RouteInfo route : mLinkProperties.getRoutes()) {
if (route.isDefaultRoute() && event.ip.equals(route.getGateway())) return true;
}
return false;
}
private boolean isNeighborDnsServer(@NonNull final NeighborEvent event) {
for (InetAddress dns : mLinkProperties.getDnsServers()) {
if (event.ip.equals(dns)) return true;
}
return false;
}
private boolean isMulticastResolicitEnabled() {
return mDependencies.isFeatureEnabled(mContext, IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
false /* defaultEnabled */);
}
public void updateLinkProperties(LinkProperties lp) {
if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
// TODO: figure out whether / how to cope with interface changes.
Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
"' does not match: " + mInterfaceParams.name);
return;
}
mLinkProperties = new LinkProperties(lp);
Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
final List<RouteInfo> routes = mLinkProperties.getRoutes();
for (RouteInfo route : routes) {
if (route.hasGateway()) {
InetAddress gw = route.getGateway();
if (isOnLink(routes, gw)) {
newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
}
}
}
for (InetAddress dns : lp.getDnsServers()) {
if (isOnLink(routes, dns)) {
newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
}
}
mNeighborWatchList = newNeighborWatchList;
if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
}
public void clearLinkProperties() {
mLinkProperties.clear();
mNeighborWatchList.clear();
if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
}
private void handleNeighborReachable(@Nullable final NeighborEvent prev,
@NonNull final NeighborEvent event) {
if (isMulticastResolicitEnabled()
&& hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
// This implies device has confirmed the neighbor's reachability from
// other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
// address hasn't changed is required. If Mac address does change, then
// trigger a new neighbor lost event and disconnect.
final String logMsg = "ALERT neighbor: " + event.ip
+ " MAC address changed from: " + prev.macAddr
+ " to: " + event.macAddr;
final NudEventType type =
getMacAddressChangedEventType(isFromProbe(), isNudFailureDueToRoam());
mLog.w(logMsg);
mCallback.notifyLost(event.ip, logMsg, type);
logNudFailed(event, type);
return;
}
maybeRestoreNeighborParameters();
}
private void handleNeighborLost(NeighborEvent event) {
final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
InetAddress ip = null;
for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
// TODO: Consider using NeighborEvent#isValid() here; it's more
// strict but may interact badly if other entries are somehow in
// NUD_INCOMPLETE (say, during network attach).
final NeighborEvent val = entry.getValue();
// Find all the neighbors that have gone into FAILED state.
// Ignore entries for which we have never received an event. If there are neighbors
// that never respond to ARP/ND, the kernel will send several FAILED events, then
// an INCOMPLETE event, and then more FAILED events. The INCOMPLETE event will
// populate the map and the subsequent FAILED event will be processed.
if (val == null || val.nudState != StructNdMsg.NUD_FAILED) continue;
ip = entry.getKey();
for (RouteInfo route : mLinkProperties.getRoutes()) {
if (ip.equals(route.getGateway())) {
whatIfLp.removeRoute(route);
}
}
if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
// We should do this unconditionally, but alas we cannot: b/31827713.
whatIfLp.removeDnsServer(ip);
}
}
final boolean lostProvisioning =
(mLinkProperties.isIpv4Provisioned() && !whatIfLp.isIpv4Provisioned())
|| (mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned());
final NudEventType type = getNudFailureEventType(isFromProbe(),
isNudFailureDueToRoam(), lostProvisioning);
if (lostProvisioning) {
final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
Log.w(TAG, logMsg);
// TODO: remove |ip| when the callback signature no longer has
// an InetAddress argument.
mCallback.notifyLost(ip, logMsg, type);
}
logNudFailed(event, type);
}
private void maybeRestoreNeighborParameters() {
for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
if (DBG) {
Log.d(TAG, "neighbour IPv4(v6): " + entry.getKey() + " neighbour state: "
+ StructNdMsg.stringForNudState(entry.getValue().nudState));
}
final NeighborEvent val = entry.getValue();
// If an entry is null, consider that probing for that neighbour has completed.
if (val == null || val.nudState != StructNdMsg.NUD_REACHABLE) return;
}
// Probing for all neighbours in the watchlist is complete and the connection is stable,
// restore NUD probe parameters to steadystate value. In the case where neighbours
// are responsive, this code will run before the wakelock expires.
setNeighbourParametersForSteadyState();
}
private boolean avoidingBadLinks() {
return !mUsingMultinetworkPolicyTracker || mCm.shouldAvoidBadWifi();
}
/**
* Force probe to verify whether or not the critical on-link neighbours are still reachable.
*
* @param dueToRoam indicate on which situation forced probe has been sent, e.g., on post
* roaming or receiving CMD_CONFIRM from IpClient.
*/
public void probeAll(boolean dueToRoam) {
setNeighbourParametersPostRoaming();
final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
if (!ipProbeList.isEmpty()) {
// Keep the CPU awake long enough to allow all ARP/ND
// probes a reasonable chance at success. See b/23197666.
//
// The wakelock we use is (by default) refcounted, and this version
// of acquire(timeout) queues a release message to keep acquisitions
// and releases balanced.
mDependencies.acquireWakeLock(getProbeWakeLockDuration());
}
for (InetAddress ip : ipProbeList) {
final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
ip.getHostAddress(), rval));
logEvent(IpReachabilityEvent.PROBE, rval);
}
mLastProbeTimeMs = SystemClock.elapsedRealtime();
if (dueToRoam) {
mLastProbeDueToRoamMs = mLastProbeTimeMs;
} else {
mLastProbeDueToConfirmMs = mLastProbeTimeMs;
}
}
private long getProbeWakeLockDuration() {
final long gracePeriodMs = 500;
final int numSolicits =
mNumSolicits + (isMulticastResolicitEnabled() ? NUD_MCAST_RESOLICIT_NUM : 0);
return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs;
}
private void setNeighbourParametersPostRoaming() {
setNeighborParametersFromResources(R.integer.config_nud_postroaming_solicit_num,
R.integer.config_nud_postroaming_solicit_interval);
}
private void setNeighbourParametersForSteadyState() {
setNeighborParametersFromResources(R.integer.config_nud_steadystate_solicit_num,
R.integer.config_nud_steadystate_solicit_interval);
}
private void setNeighborParametersFromResources(final int numResId, final int intervalResId) {
try {
final int numSolicits = mContext.getResources().getInteger(numResId);
final int interSolicitIntervalMs = mContext.getResources().getInteger(intervalResId);
setNeighborParameters(numSolicits, interSolicitIntervalMs);
} catch (Exception e) {
Log.e(TAG, "Failed to adjust neighbor parameters");
}
}
private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs)
throws RemoteException, IllegalArgumentException {
// Do not set mcast_resolicit param by default.
setNeighborParameters(numSolicits, interSolicitIntervalMs, INVALID_NUD_MCAST_RESOLICIT_NUM);
}
private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs,
int numResolicits) throws RemoteException, IllegalArgumentException {
Preconditions.checkArgument(numSolicits >= MIN_NUD_SOLICIT_NUM,
"numSolicits must be at least " + MIN_NUD_SOLICIT_NUM);
Preconditions.checkArgument(numSolicits <= MAX_NUD_SOLICIT_NUM,
"numSolicits must be at most " + MAX_NUD_SOLICIT_NUM);
Preconditions.checkArgument(interSolicitIntervalMs >= MIN_NUD_SOLICIT_INTERVAL_MS,
"interSolicitIntervalMs must be at least " + MIN_NUD_SOLICIT_INTERVAL_MS);
Preconditions.checkArgument(interSolicitIntervalMs <= MAX_NUD_SOLICIT_INTERVAL_MS,
"interSolicitIntervalMs must be at most " + MAX_NUD_SOLICIT_INTERVAL_MS);
for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "retrans_time_ms",
Integer.toString(interSolicitIntervalMs));
mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "ucast_solicit",
Integer.toString(numSolicits));
if (numResolicits != INVALID_NUD_MCAST_RESOLICIT_NUM) {
mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "mcast_resolicit",
Integer.toString(numResolicits));
}
}
mNumSolicits = numSolicits;
mInterSolicitIntervalMs = interSolicitIntervalMs;
}
private boolean isFromProbe() {
final long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
return duration < getProbeWakeLockDuration();
}
private boolean isNudFailureDueToRoam() {
if (!isFromProbe()) return false;
// Check to which probe expiry the curren timestamp gets close when NUD failure event
// happens, theoretically that indicates which probe event(due to roam or CMD_CONFIRM)
// was triggered eariler.
//
// Note that this would be incorrect if the probe or confirm was so long ago that the
// probe duration has already expired. That cannot happen because isFromProbe would return
// false.
final long probeExpiryAfterRoam = mLastProbeDueToRoamMs + getProbeWakeLockDuration();
final long probeExpiryAfterConfirm =
mLastProbeDueToConfirmMs + getProbeWakeLockDuration();
final long currentTime = SystemClock.elapsedRealtime();
return Math.abs(probeExpiryAfterRoam - currentTime)
< Math.abs(probeExpiryAfterConfirm - currentTime);
}
private void logEvent(int probeType, int errorCode) {
int eventType = probeType | (errorCode & 0xff);
mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
}
private void logNudFailed(final NeighborEvent event, final NudEventType type) {
logNeighborLostEvent(event, type);
// The legacy metrics only record whether the failure came from a probe and whether
// the network is still provisioned. They do not record provisioning failures due to
// multicast resolicits finding that the MAC address has changed.
final int eventType = legacyNudFailureType(type);
if (eventType == INVALID_LEGACY_NUD_FAILURE_TYPE) return;
mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
}
/**
* Returns the neighbor type code corresponding to the given conditions.
*/
private NudNeighborType getNeighborType(final NeighborEvent event) {
final boolean isGateway = isNeighborDefaultRouter(event);
final boolean isDnsServer = isNeighborDnsServer(event);
if (isGateway && isDnsServer) return NudNeighborType.NUD_NEIGHBOR_BOTH;
if (isGateway && !isDnsServer) return NudNeighborType.NUD_NEIGHBOR_GATEWAY;
if (!isGateway && isDnsServer) return NudNeighborType.NUD_NEIGHBOR_DNS;
return NudNeighborType.NUD_NEIGHBOR_UNKNOWN;
}
/**
* Returns the NUD failure event type code corresponding to the given conditions.
*/
private static NudEventType getNudFailureEventType(boolean isFromProbe, boolean isDueToRoam,
boolean isProvisioningLost) {
if (!isFromProbe) {
return isProvisioningLost
? NudEventType.NUD_ORGANIC_FAILED_CRITICAL
: NudEventType.NUD_ORGANIC_FAILED;
}
return isProvisioningLost
? isDueToRoam
? NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL
: NudEventType.NUD_CONFIRM_FAILED_CRITICAL
: isDueToRoam
? NudEventType.NUD_POST_ROAMING_FAILED
: NudEventType.NUD_CONFIRM_FAILED;
}
/**
* Returns the NUD failure event type code due to neighbor's MAC address has changed
* corresponding to the given conditions.
*/
private static NudEventType getMacAddressChangedEventType(boolean isFromProbe,
boolean isDueToRoam) {
return isFromProbe
? isDueToRoam
? NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED
: NudEventType.NUD_CONFIRM_MAC_ADDRESS_CHANGED
: NudEventType.NUD_ORGANIC_MAC_ADDRESS_CHANGED;
}
/**
* Log NUD failure metrics with new Westworld APIs while the function using mMetricsLog API
* still sends the legacy metrics, @see #logNudFailed.
*/
private void logNeighborLostEvent(final NeighborEvent event, final NudEventType type) {
final IpType ipType = (event.ip instanceof Inet6Address) ? IpType.IPV6 : IpType.IPV4;
mIpReachabilityMetrics.setNudIpType(ipType);
mIpReachabilityMetrics.setNudNeighborType(getNeighborType(event));
mIpReachabilityMetrics.setNudEventType(type);
mIpReachabilityMetrics.statsWrite();
}
/**
* Returns the NUD failure event type code corresponding to the given conditions.
*/
private static int legacyNudFailureType(final NudEventType type) {
switch (type) {
case NUD_POST_ROAMING_FAILED:
case NUD_CONFIRM_FAILED:
return NUD_FAILED;
case NUD_POST_ROAMING_FAILED_CRITICAL:
case NUD_CONFIRM_FAILED_CRITICAL:
return PROVISIONING_LOST;
case NUD_ORGANIC_FAILED:
return NUD_FAILED_ORGANIC;
case NUD_ORGANIC_FAILED_CRITICAL:
return PROVISIONING_LOST_ORGANIC;
default:
// Do not log legacy event
return INVALID_LEGACY_NUD_FAILURE_TYPE;
}
}
/**
* Convert the NUD critical failure event type to a int constant defined in IIpClientCallbacks.
*/
public static int nudEventTypeToInt(final NudEventType type) {
switch (type) {
case NUD_POST_ROAMING_FAILED_CRITICAL:
case NUD_POST_ROAMING_MAC_ADDRESS_CHANGED:
return ReachabilityLossReason.ROAM;
case NUD_CONFIRM_FAILED_CRITICAL:
case NUD_CONFIRM_MAC_ADDRESS_CHANGED:
return ReachabilityLossReason.CONFIRM;
case NUD_ORGANIC_FAILED_CRITICAL:
case NUD_ORGANIC_MAC_ADDRESS_CHANGED:
return ReachabilityLossReason.ORGANIC;
// For other NudEventType which won't trigger notifyLost, just ignore these events.
default:
return INVALID_REACHABILITY_LOSS_TYPE;
}
}
}