• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.ip;
18 
19 import static android.net.metrics.IpReachabilityEvent.NUD_FAILED;
20 import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
21 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
22 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
23 import static android.net.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
24 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
25 
26 import android.content.Context;
27 import android.net.ConnectivityManager;
28 import android.net.INetd;
29 import android.net.LinkProperties;
30 import android.net.RouteInfo;
31 import android.net.ip.IpNeighborMonitor.NeighborEvent;
32 import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
33 import android.net.metrics.IpConnectivityLog;
34 import android.net.metrics.IpReachabilityEvent;
35 import android.net.networkstack.aidl.ip.ReachabilityLossReason;
36 import android.net.util.SharedLog;
37 import android.os.ConditionVariable;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.PowerManager;
41 import android.os.PowerManager.WakeLock;
42 import android.os.RemoteException;
43 import android.os.SystemClock;
44 import android.stats.connectivity.IpType;
45 import android.stats.connectivity.NudEventType;
46 import android.stats.connectivity.NudNeighborType;
47 import android.text.TextUtils;
48 import android.util.Log;
49 
50 import androidx.annotation.NonNull;
51 import androidx.annotation.Nullable;
52 
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.internal.util.Preconditions;
55 import com.android.net.module.util.DeviceConfigUtils;
56 import com.android.net.module.util.InterfaceParams;
57 import com.android.net.module.util.netlink.StructNdMsg;
58 import com.android.networkstack.R;
59 import com.android.networkstack.metrics.IpReachabilityMonitorMetrics;
60 
61 import java.io.PrintWriter;
62 import java.net.Inet6Address;
63 import java.net.InetAddress;
64 import java.util.ArrayList;
65 import java.util.HashMap;
66 import java.util.List;
67 import java.util.Map;
68 
69 
70 /**
71  * IpReachabilityMonitor.
72  *
73  * Monitors on-link IP reachability and notifies callers whenever any on-link
74  * addresses of interest appear to have become unresponsive.
75  *
76  * This code does not concern itself with "why" a neighbour might have become
77  * unreachable. Instead, it primarily reacts to the kernel's notion of IP
78  * reachability for each of the neighbours we know to be critically important
79  * to normal network connectivity. As such, it is often "just the messenger":
80  * the neighbours about which it warns are already deemed by the kernel to have
81  * become unreachable.
82  *
83  *
84  * How it works:
85  *
86  *   1. The "on-link neighbours of interest" found in a given LinkProperties
87  *      instance are added to a "watch list" via #updateLinkProperties().
88  *      This usually means all default gateways and any on-link DNS servers.
89  *
90  *   2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,
91  *      RTM_DELNEIGH), watching only for neighbours in the watch list.
92  *
93  *        - A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and
94  *          even NUD_PROBE is perfectly normal; we merely record the new state.
95  *
96  *        - A neighbour's entry may be deleted (RTM_DELNEIGH), for example due
97  *          to garbage collection.  This is not necessarily of immediate
98  *          concern; we record the neighbour as moving to NUD_NONE.
99  *
100  *        - A neighbour transitioning to NUD_FAILED (for any reason) is
101  *          critically important and is handled as described below in #4.
102  *
103  *   3. All on-link neighbours in the watch list can be forcibly "probed" by
104  *      calling #probeAll(). This should be called whenever it is important to
105  *      verify that critical neighbours on the link are still reachable, e.g.
106  *      when roaming between BSSIDs.
107  *
108  *        - The kernel will send unicast ARP requests for IPv4 neighbours and
109  *          unicast NS packets for IPv6 neighbours.  The expected replies will
110  *          likely be unicast.
111  *
112  *        - The forced probing is done holding a wakelock. The kernel may,
113  *          however, initiate probing of a neighbor on its own, i.e. whenever
114  *          a neighbour has expired from NUD_DELAY.
115  *
116  *        - The kernel sends:
117  *
118  *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/ucast_solicit
119  *
120  *          number of probes (usually 3) every:
121  *
122  *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/retrans_time_ms
123  *
124  *          number of milliseconds (usually 1000ms). This normally results in
125  *          3 unicast packets, 1 per second.
126  *
127  *        - If no response is received to any of the probe packets, the kernel
128  *          marks the neighbour as being in state NUD_FAILED, and the listening
129  *          process in #2 will learn of it.
130  *
131  *   4. We call the supplied Callback#notifyLost() function if the loss of a
132  *      neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to
133  *      become incomplete (a loss of provisioning).
134  *
135  *        - For example, losing all our IPv4 on-link DNS servers (or losing
136  *          our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6)
137  *          provisioning; Callback#notifyLost() would be called.
138  *
139  *        - Since it can be non-trivial to reacquire certain IP provisioning
140  *          state it may be best for the link to disconnect completely and
141  *          reconnect afresh.
142  *
143  * Accessing an instance of this class from multiple threads is NOT safe.
144  *
145  * @hide
146  */
147 public class IpReachabilityMonitor {
148     private static final String TAG = "IpReachabilityMonitor";
149     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
150     private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
151 
152     // Upper and lower bound for NUD probe parameters.
153     protected static final int MAX_NUD_SOLICIT_NUM = 15;
154     protected static final int MIN_NUD_SOLICIT_NUM = 5;
155     protected static final int MAX_NUD_SOLICIT_INTERVAL_MS = 1000;
156     protected static final int MIN_NUD_SOLICIT_INTERVAL_MS = 750;
157     protected static final int NUD_MCAST_RESOLICIT_NUM = 3;
158     private static final int INVALID_NUD_MCAST_RESOLICIT_NUM = -1;
159 
160     private static final int INVALID_LEGACY_NUD_FAILURE_TYPE = -1;
161     public static final int INVALID_REACHABILITY_LOSS_TYPE = -1;
162 
163     public interface Callback {
164         /**
165          * This callback function must execute as quickly as possible as it is
166          * run on the same thread that listens to kernel neighbor updates.
167          *
168          * TODO: refactor to something like notifyProvisioningLost(String msg).
169          */
notifyLost(InetAddress ip, String logMsg, NudEventType type)170         void notifyLost(InetAddress ip, String logMsg, NudEventType type);
171     }
172 
173     /**
174      * Encapsulates IpReachabilityMonitor dependencies on systems that hinder unit testing.
175      * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
176      */
177     interface Dependencies {
acquireWakeLock(long durationMs)178         void acquireWakeLock(long durationMs);
makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb)179         IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb);
isFeatureEnabled(Context context, String name, boolean defaultEnabled)180         boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled);
getIpReachabilityMonitorMetrics()181         IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics();
182 
makeDefault(Context context, String iface)183         static Dependencies makeDefault(Context context, String iface) {
184             final String lockName = TAG + "." + iface;
185             final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
186             final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
187 
188             return new Dependencies() {
189                 public void acquireWakeLock(long durationMs) {
190                     lock.acquire(durationMs);
191                 }
192 
193                 public IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log,
194                         NeighborEventConsumer cb) {
195                     return new IpNeighborMonitor(h, log, cb);
196                 }
197 
198                 public boolean isFeatureEnabled(final Context context, final String name,
199                         boolean defaultEnabled) {
200                     return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
201                             defaultEnabled);
202                 }
203 
204                 public IpReachabilityMonitorMetrics getIpReachabilityMonitorMetrics() {
205                     return new IpReachabilityMonitorMetrics();
206                 }
207             };
208         }
209     }
210 
211     private final InterfaceParams mInterfaceParams;
212     private final IpNeighborMonitor mIpNeighborMonitor;
213     private final SharedLog mLog;
214     private final Dependencies mDependencies;
215     private final boolean mUsingMultinetworkPolicyTracker;
216     private final ConnectivityManager mCm;
217     private final IpConnectivityLog mMetricsLog;
218     private final Context mContext;
219     private final INetd mNetd;
220     private final IpReachabilityMonitorMetrics mIpReachabilityMetrics;
221     private LinkProperties mLinkProperties = new LinkProperties();
222     private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
223     // Time in milliseconds of the last forced probe request.
224     private volatile long mLastProbeTimeMs;
225     // Time in milliseconds of the last forced probe request due to roam or CMD_CONFIRM.
226     private long mLastProbeDueToRoamMs;
227     private long mLastProbeDueToConfirmMs;
228     private int mNumSolicits;
229     private int mInterSolicitIntervalMs;
230     @NonNull
231     private final Callback mCallback;
232 
233     public IpReachabilityMonitor(
234             Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
235             boolean usingMultinetworkPolicyTracker, Dependencies dependencies, final INetd netd) {
236         this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker, dependencies,
237                 new IpConnectivityLog(), netd);
238     }
239 
240     @VisibleForTesting
241     IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log,
242             Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies,
243             final IpConnectivityLog metricsLog, final INetd netd) {
244         if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
245 
246         mContext = context;
247         mInterfaceParams = ifParams;
248         mLog = log.forSubComponent(TAG);
249         mCallback = callback;
250         mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
251         mCm = context.getSystemService(ConnectivityManager.class);
252         mDependencies = dependencies;
253         mMetricsLog = metricsLog;
254         mNetd = netd;
255         Preconditions.checkNotNull(mNetd);
256         Preconditions.checkArgument(!TextUtils.isEmpty(mInterfaceParams.name));
257 
258         // In case the overylaid parameters specify an invalid configuration, set the parameters
259         // to the hardcoded defaults first, then set them to the values used in the steady state.
260         try {
261             int numResolicits = isMulticastResolicitEnabled()
262                     ? NUD_MCAST_RESOLICIT_NUM
263                     : INVALID_NUD_MCAST_RESOLICIT_NUM;
264             setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
265         } catch (Exception e) {
266             Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
267         }
268         setNeighbourParametersForSteadyState();
269 
270         mIpNeighborMonitor = mDependencies.makeIpNeighborMonitor(h, mLog,
271                 (NeighborEvent event) -> {
272                     if (mInterfaceParams.index != event.ifindex) return;
273                     if (!mNeighborWatchList.containsKey(event.ip)) return;
274 
275                     final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
276 
277                     // TODO: Consider what to do with other states that are not within
278                     // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
279                     if (event.nudState == StructNdMsg.NUD_FAILED) {
280                         // After both unicast probe and multicast probe(if mcast_resolicit is not 0)
281                         // attempts fail, trigger the neighbor lost event and disconnect.
282                         mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
283                         handleNeighborLost(event);
284                     } else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
285                         handleNeighborReachable(prev, event);
286                     }
287                 });
288         mIpNeighborMonitor.start();
289         mIpReachabilityMetrics = dependencies.getIpReachabilityMonitorMetrics();
290     }
291 
292     public void stop() {
293         mIpNeighborMonitor.stop();
294         clearLinkProperties();
295     }
296 
297     public void dump(PrintWriter pw) {
298         if (Looper.myLooper() == mIpNeighborMonitor.getHandler().getLooper()) {
299             pw.println(describeWatchList("\n"));
300             return;
301         }
302 
303         final ConditionVariable cv = new ConditionVariable(false);
304         mIpNeighborMonitor.getHandler().post(() -> {
305             pw.println(describeWatchList("\n"));
306             cv.open();
307         });
308 
309         if (!cv.block(1000)) {
310             pw.println("Timed out waiting for IpReachabilityMonitor dump");
311         }
312     }
313 
314     private String describeWatchList() { return describeWatchList(" "); }
315 
316     private String describeWatchList(String sep) {
317         final StringBuilder sb = new StringBuilder();
318         sb.append("iface{" + mInterfaceParams + "}," + sep);
319         sb.append("ntable=[" + sep);
320         String delimiter = "";
321         for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
322             sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
323             delimiter = "," + sep;
324         }
325         sb.append("]");
326         return sb.toString();
327     }
328 
329     @VisibleForTesting
330     static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
331         for (RouteInfo route : routes) {
332             if (!route.hasGateway() && route.matches(ip)
333                     && route.getType() == RouteInfo.RTN_UNICAST) {
334                 return true;
335             }
336         }
337         return false;
338     }
339 
340     private boolean hasDefaultRouterNeighborMacAddressChanged(
341             @Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) {
342         if (prev == null || !isNeighborDefaultRouter(event)) return false;
343         return !event.macAddr.equals(prev.macAddr);
344     }
345 
346     private boolean isNeighborDefaultRouter(@NonNull final NeighborEvent event) {
347         // For the IPv6 link-local scoped address, equals() works because the NeighborEvent.ip
348         // doesn't have a scope id and Inet6Address#equals doesn't consider scope id neither.
349         for (RouteInfo route : mLinkProperties.getRoutes()) {
350             if (route.isDefaultRoute() && event.ip.equals(route.getGateway())) return true;
351         }
352         return false;
353     }
354 
355     private boolean isNeighborDnsServer(@NonNull final NeighborEvent event) {
356         for (InetAddress dns : mLinkProperties.getDnsServers()) {
357             if (event.ip.equals(dns)) return true;
358         }
359         return false;
360     }
361 
362     private boolean isMulticastResolicitEnabled() {
363         return mDependencies.isFeatureEnabled(mContext, IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
364                 false /* defaultEnabled */);
365     }
366 
367     public void updateLinkProperties(LinkProperties lp) {
368         if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
369             // TODO: figure out whether / how to cope with interface changes.
370             Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
371                     "' does not match: " + mInterfaceParams.name);
372             return;
373         }
374 
375         mLinkProperties = new LinkProperties(lp);
376         Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
377 
378         final List<RouteInfo> routes = mLinkProperties.getRoutes();
379         for (RouteInfo route : routes) {
380             if (route.hasGateway()) {
381                 InetAddress gw = route.getGateway();
382                 if (isOnLink(routes, gw)) {
383                     newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
384                 }
385             }
386         }
387 
388         for (InetAddress dns : lp.getDnsServers()) {
389             if (isOnLink(routes, dns)) {
390                 newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
391             }
392         }
393 
394         mNeighborWatchList = newNeighborWatchList;
395         if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
396     }
397 
398     public void clearLinkProperties() {
399         mLinkProperties.clear();
400         mNeighborWatchList.clear();
401         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
402     }
403 
404     private void handleNeighborReachable(@Nullable final NeighborEvent prev,
405             @NonNull final NeighborEvent event) {
406         if (isMulticastResolicitEnabled()
407                 && hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
408             // This implies device has confirmed the neighbor's reachability from
409             // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
410             // address hasn't changed is required. If Mac address does change, then
411             // trigger a new neighbor lost event and disconnect.
412             final String logMsg = "ALERT neighbor: " + event.ip
413                     + " MAC address changed from: " + prev.macAddr
414                     + " to: " + event.macAddr;
415             final NudEventType type =
416                     getMacAddressChangedEventType(isFromProbe(), isNudFailureDueToRoam());
417             mLog.w(logMsg);
418             mCallback.notifyLost(event.ip, logMsg, type);
419             logNudFailed(event, type);
420             return;
421         }
422         maybeRestoreNeighborParameters();
423     }
424 
425     private void handleNeighborLost(NeighborEvent event) {
426         final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
427 
428         InetAddress ip = null;
429         for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
430             // TODO: Consider using NeighborEvent#isValid() here; it's more
431             // strict but may interact badly if other entries are somehow in
432             // NUD_INCOMPLETE (say, during network attach).
433             final NeighborEvent val = entry.getValue();
434 
435             // Find all the neighbors that have gone into FAILED state.
436             // Ignore entries for which we have never received an event. If there are neighbors
437             // that never respond to ARP/ND, the kernel will send several FAILED events, then
438             // an INCOMPLETE event, and then more FAILED events. The INCOMPLETE event will
439             // populate the map and the subsequent FAILED event will be processed.
440             if (val == null || val.nudState != StructNdMsg.NUD_FAILED) continue;
441 
442             ip = entry.getKey();
443             for (RouteInfo route : mLinkProperties.getRoutes()) {
444                 if (ip.equals(route.getGateway())) {
445                     whatIfLp.removeRoute(route);
446                 }
447             }
448 
449             if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
450                 // We should do this unconditionally, but alas we cannot: b/31827713.
451                 whatIfLp.removeDnsServer(ip);
452             }
453         }
454 
455         final boolean lostProvisioning =
456                 (mLinkProperties.isIpv4Provisioned() && !whatIfLp.isIpv4Provisioned())
457                 || (mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned());
458         final NudEventType type = getNudFailureEventType(isFromProbe(),
459                 isNudFailureDueToRoam(), lostProvisioning);
460 
461         if (lostProvisioning) {
462             final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
463             Log.w(TAG, logMsg);
464             // TODO: remove |ip| when the callback signature no longer has
465             // an InetAddress argument.
466             mCallback.notifyLost(ip, logMsg, type);
467         }
468         logNudFailed(event, type);
469     }
470 
471     private void maybeRestoreNeighborParameters() {
472         for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
473             if (DBG) {
474                 Log.d(TAG, "neighbour IPv4(v6): " + entry.getKey() + " neighbour state: "
475                         + StructNdMsg.stringForNudState(entry.getValue().nudState));
476             }
477             final NeighborEvent val = entry.getValue();
478             // If an entry is null, consider that probing for that neighbour has completed.
479             if (val == null || val.nudState != StructNdMsg.NUD_REACHABLE) return;
480         }
481 
482         // Probing for all neighbours in the watchlist is complete and the connection is stable,
483         // restore NUD probe parameters to steadystate value. In the case where neighbours
484         // are responsive, this code will run before the wakelock expires.
485         setNeighbourParametersForSteadyState();
486     }
487 
488     private boolean avoidingBadLinks() {
489         return !mUsingMultinetworkPolicyTracker || mCm.shouldAvoidBadWifi();
490     }
491 
492     /**
493      * Force probe to verify whether or not the critical on-link neighbours are still reachable.
494      *
495      * @param dueToRoam indicate on which situation forced probe has been sent, e.g., on post
496      *                  roaming or receiving CMD_CONFIRM from IpClient.
497      */
498     public void probeAll(boolean dueToRoam) {
499         setNeighbourParametersPostRoaming();
500 
501         final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
502         if (!ipProbeList.isEmpty()) {
503             // Keep the CPU awake long enough to allow all ARP/ND
504             // probes a reasonable chance at success. See b/23197666.
505             //
506             // The wakelock we use is (by default) refcounted, and this version
507             // of acquire(timeout) queues a release message to keep acquisitions
508             // and releases balanced.
509             mDependencies.acquireWakeLock(getProbeWakeLockDuration());
510         }
511 
512         for (InetAddress ip : ipProbeList) {
513             final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
514             mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
515                      ip.getHostAddress(), rval));
516             logEvent(IpReachabilityEvent.PROBE, rval);
517         }
518         mLastProbeTimeMs = SystemClock.elapsedRealtime();
519         if (dueToRoam) {
520             mLastProbeDueToRoamMs = mLastProbeTimeMs;
521         } else {
522             mLastProbeDueToConfirmMs = mLastProbeTimeMs;
523         }
524     }
525 
526     private long getProbeWakeLockDuration() {
527         final long gracePeriodMs = 500;
528         final int numSolicits =
529                 mNumSolicits + (isMulticastResolicitEnabled() ? NUD_MCAST_RESOLICIT_NUM : 0);
530         return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs;
531     }
532 
533     private void setNeighbourParametersPostRoaming() {
534         setNeighborParametersFromResources(R.integer.config_nud_postroaming_solicit_num,
535                 R.integer.config_nud_postroaming_solicit_interval);
536     }
537 
538     private void setNeighbourParametersForSteadyState() {
539         setNeighborParametersFromResources(R.integer.config_nud_steadystate_solicit_num,
540                 R.integer.config_nud_steadystate_solicit_interval);
541     }
542 
543     private void setNeighborParametersFromResources(final int numResId, final int intervalResId) {
544         try {
545             final int numSolicits = mContext.getResources().getInteger(numResId);
546             final int interSolicitIntervalMs = mContext.getResources().getInteger(intervalResId);
547             setNeighborParameters(numSolicits, interSolicitIntervalMs);
548         } catch (Exception e) {
549             Log.e(TAG, "Failed to adjust neighbor parameters");
550         }
551     }
552 
553     private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs)
554             throws RemoteException, IllegalArgumentException {
555         // Do not set mcast_resolicit param by default.
556         setNeighborParameters(numSolicits, interSolicitIntervalMs, INVALID_NUD_MCAST_RESOLICIT_NUM);
557     }
558 
559     private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs,
560             int numResolicits) throws RemoteException, IllegalArgumentException {
561         Preconditions.checkArgument(numSolicits >= MIN_NUD_SOLICIT_NUM,
562                 "numSolicits must be at least " + MIN_NUD_SOLICIT_NUM);
563         Preconditions.checkArgument(numSolicits <= MAX_NUD_SOLICIT_NUM,
564                 "numSolicits must be at most " + MAX_NUD_SOLICIT_NUM);
565         Preconditions.checkArgument(interSolicitIntervalMs >= MIN_NUD_SOLICIT_INTERVAL_MS,
566                 "interSolicitIntervalMs must be at least " + MIN_NUD_SOLICIT_INTERVAL_MS);
567         Preconditions.checkArgument(interSolicitIntervalMs <= MAX_NUD_SOLICIT_INTERVAL_MS,
568                 "interSolicitIntervalMs must be at most " + MAX_NUD_SOLICIT_INTERVAL_MS);
569 
570         for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) {
571             mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "retrans_time_ms",
572                     Integer.toString(interSolicitIntervalMs));
573             mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "ucast_solicit",
574                     Integer.toString(numSolicits));
575             if (numResolicits != INVALID_NUD_MCAST_RESOLICIT_NUM) {
576                 mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "mcast_resolicit",
577                         Integer.toString(numResolicits));
578             }
579         }
580 
581         mNumSolicits = numSolicits;
582         mInterSolicitIntervalMs = interSolicitIntervalMs;
583     }
584 
585     private boolean isFromProbe() {
586         final long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
587         return duration < getProbeWakeLockDuration();
588     }
589 
590     private boolean isNudFailureDueToRoam() {
591         if (!isFromProbe()) return false;
592 
593         // Check to which probe expiry the curren timestamp gets close when NUD failure event
594         // happens, theoretically that indicates which probe event(due to roam or CMD_CONFIRM)
595         // was triggered eariler.
596         //
597         // Note that this would be incorrect if the probe or confirm was so long ago that the
598         // probe duration has already expired. That cannot happen because isFromProbe would return
599         // false.
600         final long probeExpiryAfterRoam = mLastProbeDueToRoamMs + getProbeWakeLockDuration();
601         final long probeExpiryAfterConfirm =
602                 mLastProbeDueToConfirmMs + getProbeWakeLockDuration();
603         final long currentTime = SystemClock.elapsedRealtime();
604         return Math.abs(probeExpiryAfterRoam - currentTime)
605                 < Math.abs(probeExpiryAfterConfirm - currentTime);
606     }
607 
608     private void logEvent(int probeType, int errorCode) {
609         int eventType = probeType | (errorCode & 0xff);
610         mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
611     }
612 
613     private void logNudFailed(final NeighborEvent event, final NudEventType type) {
614         logNeighborLostEvent(event, type);
615 
616         // The legacy metrics only record whether the failure came from a probe and whether
617         // the network is still provisioned. They do not record provisioning failures due to
618         // multicast resolicits finding that the MAC address has changed.
619         final int eventType = legacyNudFailureType(type);
620         if (eventType == INVALID_LEGACY_NUD_FAILURE_TYPE) return;
621         mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
622     }
623 
624     /**
625      * Returns the neighbor type code corresponding to the given conditions.
626      */
627     private NudNeighborType getNeighborType(final NeighborEvent event) {
628         final boolean isGateway = isNeighborDefaultRouter(event);
629         final boolean isDnsServer = isNeighborDnsServer(event);
630 
631         if (isGateway && isDnsServer) return NudNeighborType.NUD_NEIGHBOR_BOTH;
632         if (isGateway && !isDnsServer) return NudNeighborType.NUD_NEIGHBOR_GATEWAY;
633         if (!isGateway && isDnsServer) return NudNeighborType.NUD_NEIGHBOR_DNS;
634         return NudNeighborType.NUD_NEIGHBOR_UNKNOWN;
635     }
636 
637     /**
638      * Returns the NUD failure event type code corresponding to the given conditions.
639      */
640     private static NudEventType getNudFailureEventType(boolean isFromProbe, boolean isDueToRoam,
641             boolean isProvisioningLost) {
642         if (!isFromProbe) {
643             return isProvisioningLost
644                     ? NudEventType.NUD_ORGANIC_FAILED_CRITICAL
645                     : NudEventType.NUD_ORGANIC_FAILED;
646         }
647         return isProvisioningLost
648                 ? isDueToRoam
649                         ? NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL
650                         : NudEventType.NUD_CONFIRM_FAILED_CRITICAL
651                 : isDueToRoam
652                         ? NudEventType.NUD_POST_ROAMING_FAILED
653                         : NudEventType.NUD_CONFIRM_FAILED;
654     }
655 
656     /**
657      * Returns the NUD failure event type code due to neighbor's MAC address has changed
658      * corresponding to the given conditions.
659      */
660     private static NudEventType getMacAddressChangedEventType(boolean isFromProbe,
661             boolean isDueToRoam) {
662         return isFromProbe
663                 ? isDueToRoam
664                         ? NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED
665                         : NudEventType.NUD_CONFIRM_MAC_ADDRESS_CHANGED
666                 : NudEventType.NUD_ORGANIC_MAC_ADDRESS_CHANGED;
667     }
668 
669     /**
670      * Log NUD failure metrics with new statsd APIs while the function using mMetricsLog API
671      * still sends the legacy metrics, @see #logNudFailed.
672      */
673     private void logNeighborLostEvent(final NeighborEvent event, final NudEventType type) {
674         final IpType ipType = (event.ip instanceof Inet6Address) ? IpType.IPV6 : IpType.IPV4;
675         mIpReachabilityMetrics.setNudIpType(ipType);
676         mIpReachabilityMetrics.setNudNeighborType(getNeighborType(event));
677         mIpReachabilityMetrics.setNudEventType(type);
678         mIpReachabilityMetrics.statsWrite();
679     }
680 
681     /**
682      * Returns the NUD failure event type code corresponding to the given conditions.
683      */
684     private static int legacyNudFailureType(final NudEventType type) {
685         switch (type) {
686             case NUD_POST_ROAMING_FAILED:
687             case NUD_CONFIRM_FAILED:
688                 return NUD_FAILED;
689             case NUD_POST_ROAMING_FAILED_CRITICAL:
690             case NUD_CONFIRM_FAILED_CRITICAL:
691                 return PROVISIONING_LOST;
692             case NUD_ORGANIC_FAILED:
693                 return NUD_FAILED_ORGANIC;
694             case NUD_ORGANIC_FAILED_CRITICAL:
695                 return PROVISIONING_LOST_ORGANIC;
696             default:
697                 // Do not log legacy event
698                 return INVALID_LEGACY_NUD_FAILURE_TYPE;
699         }
700     }
701 
702     /**
703      * Convert the NUD critical failure event type to a int constant defined in IIpClientCallbacks.
704      */
705     public static int nudEventTypeToInt(final NudEventType type) {
706         switch (type) {
707             case NUD_POST_ROAMING_FAILED_CRITICAL:
708             case NUD_POST_ROAMING_MAC_ADDRESS_CHANGED:
709                 return ReachabilityLossReason.ROAM;
710             case NUD_CONFIRM_FAILED_CRITICAL:
711             case NUD_CONFIRM_MAC_ADDRESS_CHANGED:
712                 return ReachabilityLossReason.CONFIRM;
713             case NUD_ORGANIC_FAILED_CRITICAL:
714             case NUD_ORGANIC_MAC_ADDRESS_CHANGED:
715                 return ReachabilityLossReason.ORGANIC;
716             // For other NudEventType which won't trigger notifyLost, just ignore these events.
717             default:
718                 return INVALID_REACHABILITY_LOSS_TYPE;
719         }
720     }
721 }
722