Home | History | Annotate | Download | only in ip
      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 com.android.internal.annotations.GuardedBy;
     20 
     21 import android.content.Context;
     22 import android.net.LinkAddress;
     23 import android.net.LinkProperties;
     24 import android.net.LinkProperties.ProvisioningChange;
     25 import android.net.ProxyInfo;
     26 import android.net.RouteInfo;
     27 import android.net.metrics.IpConnectivityLog;
     28 import android.net.metrics.IpReachabilityEvent;
     29 import android.net.netlink.NetlinkConstants;
     30 import android.net.netlink.NetlinkErrorMessage;
     31 import android.net.netlink.NetlinkMessage;
     32 import android.net.netlink.NetlinkSocket;
     33 import android.net.netlink.RtNetlinkNeighborMessage;
     34 import android.net.netlink.StructNdaCacheInfo;
     35 import android.net.netlink.StructNdMsg;
     36 import android.net.netlink.StructNlMsgHdr;
     37 import android.net.util.MultinetworkPolicyTracker;
     38 import android.os.PowerManager;
     39 import android.os.SystemClock;
     40 import android.system.ErrnoException;
     41 import android.system.NetlinkSocketAddress;
     42 import android.system.OsConstants;
     43 import android.util.Log;
     44 
     45 import java.io.InterruptedIOException;
     46 import java.net.Inet6Address;
     47 import java.net.InetAddress;
     48 import java.net.InetSocketAddress;
     49 import java.net.NetworkInterface;
     50 import java.net.SocketAddress;
     51 import java.net.SocketException;
     52 import java.nio.ByteBuffer;
     53 import java.util.ArrayList;
     54 import java.util.Arrays;
     55 import java.util.HashMap;
     56 import java.util.List;
     57 import java.util.Map;
     58 import java.util.Set;
     59 
     60 
     61 /**
     62  * IpReachabilityMonitor.
     63  *
     64  * Monitors on-link IP reachability and notifies callers whenever any on-link
     65  * addresses of interest appear to have become unresponsive.
     66  *
     67  * This code does not concern itself with "why" a neighbour might have become
     68  * unreachable. Instead, it primarily reacts to the kernel's notion of IP
     69  * reachability for each of the neighbours we know to be critically important
     70  * to normal network connectivity. As such, it is often "just the messenger":
     71  * the neighbours about which it warns are already deemed by the kernel to have
     72  * become unreachable.
     73  *
     74  *
     75  * How it works:
     76  *
     77  *   1. The "on-link neighbours of interest" found in a given LinkProperties
     78  *      instance are added to a "watch list" via #updateLinkProperties().
     79  *      This usually means all default gateways and any on-link DNS servers.
     80  *
     81  *   2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,
     82  *      RTM_DELNEIGH), watching only for neighbours in the watch list.
     83  *
     84  *        - A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and
     85  *          even NUD_PROBE is perfectly normal; we merely record the new state.
     86  *
     87  *        - A neighbour's entry may be deleted (RTM_DELNEIGH), for example due
     88  *          to garbage collection.  This is not necessarily of immediate
     89  *          concern; we record the neighbour as moving to NUD_NONE.
     90  *
     91  *        - A neighbour transitioning to NUD_FAILED (for any reason) is
     92  *          critically important and is handled as described below in #4.
     93  *
     94  *   3. All on-link neighbours in the watch list can be forcibly "probed" by
     95  *      calling #probeAll(). This should be called whenever it is important to
     96  *      verify that critical neighbours on the link are still reachable, e.g.
     97  *      when roaming between BSSIDs.
     98  *
     99  *        - The kernel will send unicast ARP requests for IPv4 neighbours and
    100  *          unicast NS packets for IPv6 neighbours.  The expected replies will
    101  *          likely be unicast.
    102  *
    103  *        - The forced probing is done holding a wakelock. The kernel may,
    104  *          however, initiate probing of a neighbor on its own, i.e. whenever
    105  *          a neighbour has expired from NUD_DELAY.
    106  *
    107  *        - The kernel sends:
    108  *
    109  *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/ucast_solicit
    110  *
    111  *          number of probes (usually 3) every:
    112  *
    113  *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/retrans_time_ms
    114  *
    115  *          number of milliseconds (usually 1000ms). This normally results in
    116  *          3 unicast packets, 1 per second.
    117  *
    118  *        - If no response is received to any of the probe packets, the kernel
    119  *          marks the neighbour as being in state NUD_FAILED, and the listening
    120  *          process in #2 will learn of it.
    121  *
    122  *   4. We call the supplied Callback#notifyLost() function if the loss of a
    123  *      neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to
    124  *      become incomplete (a loss of provisioning).
    125  *
    126  *        - For example, losing all our IPv4 on-link DNS servers (or losing
    127  *          our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6)
    128  *          provisioning; Callback#notifyLost() would be called.
    129  *
    130  *        - Since it can be non-trivial to reacquire certain IP provisioning
    131  *          state it may be best for the link to disconnect completely and
    132  *          reconnect afresh.
    133  *
    134  * @hide
    135  */
    136 public class IpReachabilityMonitor {
    137     private static final String TAG = "IpReachabilityMonitor";
    138     private static final boolean DBG = false;
    139     private static final boolean VDBG = false;
    140 
    141     public interface Callback {
    142         // This callback function must execute as quickly as possible as it is
    143         // run on the same thread that listens to kernel neighbor updates.
    144         //
    145         // TODO: refactor to something like notifyProvisioningLost(String msg).
    146         public void notifyLost(InetAddress ip, String logMsg);
    147     }
    148 
    149     private final Object mLock = new Object();
    150     private final PowerManager.WakeLock mWakeLock;
    151     private final String mInterfaceName;
    152     private final int mInterfaceIndex;
    153     private final Callback mCallback;
    154     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
    155     private final NetlinkSocketObserver mNetlinkSocketObserver;
    156     private final Thread mObserverThread;
    157     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
    158     @GuardedBy("mLock")
    159     private LinkProperties mLinkProperties = new LinkProperties();
    160     // TODO: consider a map to a private NeighborState class holding more
    161     // information than a single NUD state entry.
    162     @GuardedBy("mLock")
    163     private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
    164     @GuardedBy("mLock")
    165     private int mIpWatchListVersion;
    166     private volatile boolean mRunning;
    167     // Time in milliseconds of the last forced probe request.
    168     private volatile long mLastProbeTimeMs;
    169 
    170     /**
    171      * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
    172      * for the given IP address on the specified interface index.
    173      *
    174      * @return 0 if the request was successfully passed to the kernel; otherwise return
    175      *         a non-zero error code.
    176      */
    177     private static int probeNeighbor(int ifIndex, InetAddress ip) {
    178         final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
    179         if (DBG) { Log.d(TAG, msgSnippet); }
    180 
    181         final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
    182                 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
    183 
    184         int errno = -OsConstants.EPROTO;
    185         try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) {
    186             final long IO_TIMEOUT = 300L;
    187             nlSocket.connectToKernel();
    188             nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
    189             final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
    190             // recvMessage() guaranteed to not return null if it did not throw.
    191             final NetlinkMessage response = NetlinkMessage.parse(bytes);
    192             if (response != null && response instanceof NetlinkErrorMessage &&
    193                     (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
    194                 errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
    195                 if (errno != 0) {
    196                     // TODO: consider ignoring EINVAL (-22), which appears to be
    197                     // normal when probing a neighbor for which the kernel does
    198                     // not already have / no longer has a link layer address.
    199                     Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + response.toString());
    200                 }
    201             } else {
    202                 String errmsg;
    203                 if (response == null) {
    204                     bytes.position(0);
    205                     errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
    206                 } else {
    207                     errmsg = response.toString();
    208                 }
    209                 Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + errmsg);
    210             }
    211         } catch (ErrnoException e) {
    212             Log.e(TAG, "Error " + msgSnippet, e);
    213             errno = -e.errno;
    214         } catch (InterruptedIOException e) {
    215             Log.e(TAG, "Error " + msgSnippet, e);
    216             errno = -OsConstants.ETIMEDOUT;
    217         } catch (SocketException e) {
    218             Log.e(TAG, "Error " + msgSnippet, e);
    219             errno = -OsConstants.EIO;
    220         }
    221         return errno;
    222     }
    223 
    224     public IpReachabilityMonitor(Context context, String ifName, Callback callback) {
    225         this(context, ifName, callback, null);
    226     }
    227 
    228     public IpReachabilityMonitor(Context context, String ifName, Callback callback,
    229             MultinetworkPolicyTracker tracker) throws IllegalArgumentException {
    230         mInterfaceName = ifName;
    231         int ifIndex = -1;
    232         try {
    233             NetworkInterface netIf = NetworkInterface.getByName(ifName);
    234             mInterfaceIndex = netIf.getIndex();
    235         } catch (SocketException | NullPointerException e) {
    236             throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e);
    237         }
    238         mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
    239                 PowerManager.PARTIAL_WAKE_LOCK, TAG + "." + mInterfaceName);
    240         mCallback = callback;
    241         mMultinetworkPolicyTracker = tracker;
    242         mNetlinkSocketObserver = new NetlinkSocketObserver();
    243         mObserverThread = new Thread(mNetlinkSocketObserver);
    244         mObserverThread.start();
    245     }
    246 
    247     public void stop() {
    248         mRunning = false;
    249         clearLinkProperties();
    250         mNetlinkSocketObserver.clearNetlinkSocket();
    251     }
    252 
    253     // TODO: add a public dump() method that can be called during a bug report.
    254 
    255     private String describeWatchList() {
    256         final String delimiter = ", ";
    257         StringBuilder sb = new StringBuilder();
    258         synchronized (mLock) {
    259             sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, ");
    260             sb.append("v{" + mIpWatchListVersion + "}, ");
    261             sb.append("ntable=[");
    262             boolean firstTime = true;
    263             for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
    264                 if (firstTime) {
    265                     firstTime = false;
    266                 } else {
    267                     sb.append(delimiter);
    268                 }
    269                 sb.append(entry.getKey().getHostAddress() + "/" +
    270                         StructNdMsg.stringForNudState(entry.getValue()));
    271             }
    272             sb.append("]");
    273         }
    274         return sb.toString();
    275     }
    276 
    277     private boolean isWatching(InetAddress ip) {
    278         synchronized (mLock) {
    279             return mRunning && mIpWatchList.containsKey(ip);
    280         }
    281     }
    282 
    283     private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
    284         for (RouteInfo route : routes) {
    285             if (!route.hasGateway() && route.matches(ip)) {
    286                 return true;
    287             }
    288         }
    289         return false;
    290     }
    291 
    292     private short getNeighborStateLocked(InetAddress ip) {
    293         if (mIpWatchList.containsKey(ip)) {
    294             return mIpWatchList.get(ip);
    295         }
    296         return StructNdMsg.NUD_NONE;
    297     }
    298 
    299     public void updateLinkProperties(LinkProperties lp) {
    300         if (!mInterfaceName.equals(lp.getInterfaceName())) {
    301             // TODO: figure out whether / how to cope with interface changes.
    302             Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
    303                     "' does not match: " + mInterfaceName);
    304             return;
    305         }
    306 
    307         synchronized (mLock) {
    308             mLinkProperties = new LinkProperties(lp);
    309             Map<InetAddress, Short> newIpWatchList = new HashMap<>();
    310 
    311             final List<RouteInfo> routes = mLinkProperties.getRoutes();
    312             for (RouteInfo route : routes) {
    313                 if (route.hasGateway()) {
    314                     InetAddress gw = route.getGateway();
    315                     if (isOnLink(routes, gw)) {
    316                         newIpWatchList.put(gw, getNeighborStateLocked(gw));
    317                     }
    318                 }
    319             }
    320 
    321             for (InetAddress nameserver : lp.getDnsServers()) {
    322                 if (isOnLink(routes, nameserver)) {
    323                     newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));
    324                 }
    325             }
    326 
    327             mIpWatchList = newIpWatchList;
    328             mIpWatchListVersion++;
    329         }
    330         if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
    331     }
    332 
    333     public void clearLinkProperties() {
    334         synchronized (mLock) {
    335             mLinkProperties.clear();
    336             mIpWatchList.clear();
    337             mIpWatchListVersion++;
    338         }
    339         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
    340     }
    341 
    342     private void handleNeighborLost(String msg) {
    343         InetAddress ip = null;
    344         final ProvisioningChange delta;
    345         synchronized (mLock) {
    346             LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
    347 
    348             for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
    349                 if (entry.getValue() != StructNdMsg.NUD_FAILED) {
    350                     continue;
    351                 }
    352 
    353                 ip = entry.getKey();
    354                 for (RouteInfo route : mLinkProperties.getRoutes()) {
    355                     if (ip.equals(route.getGateway())) {
    356                         whatIfLp.removeRoute(route);
    357                     }
    358                 }
    359 
    360                 if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
    361                     // We should do this unconditionally, but alas we cannot: b/31827713.
    362                     whatIfLp.removeDnsServer(ip);
    363                 }
    364             }
    365 
    366             delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
    367         }
    368 
    369         if (delta == ProvisioningChange.LOST_PROVISIONING) {
    370             final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
    371             Log.w(TAG, logMsg);
    372             if (mCallback != null) {
    373                 // TODO: remove |ip| when the callback signature no longer has
    374                 // an InetAddress argument.
    375                 mCallback.notifyLost(ip, logMsg);
    376             }
    377         }
    378         logNudFailed(delta);
    379     }
    380 
    381     private boolean avoidingBadLinks() {
    382         return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi();
    383     }
    384 
    385     public void probeAll() {
    386         final List<InetAddress> ipProbeList;
    387         synchronized (mLock) {
    388             ipProbeList = new ArrayList<>(mIpWatchList.keySet());
    389         }
    390 
    391         if (!ipProbeList.isEmpty() && mRunning) {
    392             // Keep the CPU awake long enough to allow all ARP/ND
    393             // probes a reasonable chance at success. See b/23197666.
    394             //
    395             // The wakelock we use is (by default) refcounted, and this version
    396             // of acquire(timeout) queues a release message to keep acquisitions
    397             // and releases balanced.
    398             mWakeLock.acquire(getProbeWakeLockDuration());
    399         }
    400 
    401         for (InetAddress target : ipProbeList) {
    402             if (!mRunning) {
    403                 break;
    404             }
    405             final int returnValue = probeNeighbor(mInterfaceIndex, target);
    406             logEvent(IpReachabilityEvent.PROBE, returnValue);
    407         }
    408         mLastProbeTimeMs = SystemClock.elapsedRealtime();
    409     }
    410 
    411     private static long getProbeWakeLockDuration() {
    412         // Ideally, this would be computed by examining the values of:
    413         //
    414         //     /proc/sys/net/ipv[46]/neigh/<ifname>/ucast_solicit
    415         //
    416         // and:
    417         //
    418         //     /proc/sys/net/ipv[46]/neigh/<ifname>/retrans_time_ms
    419         //
    420         // For now, just make some assumptions.
    421         final long numUnicastProbes = 3;
    422         final long retransTimeMs = 1000;
    423         final long gracePeriodMs = 500;
    424         return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
    425     }
    426 
    427     private void logEvent(int probeType, int errorCode) {
    428         int eventType = probeType | (errorCode & 0xff);
    429         mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
    430     }
    431 
    432     private void logNudFailed(ProvisioningChange delta) {
    433         long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
    434         boolean isFromProbe = (duration < getProbeWakeLockDuration());
    435         boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
    436         int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
    437         mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
    438     }
    439 
    440     // TODO: simplify the number of objects by making this extend Thread.
    441     private final class NetlinkSocketObserver implements Runnable {
    442         private NetlinkSocket mSocket;
    443 
    444         @Override
    445         public void run() {
    446             if (VDBG) { Log.d(TAG, "Starting observing thread."); }
    447             mRunning = true;
    448 
    449             try {
    450                 setupNetlinkSocket();
    451             } catch (ErrnoException | SocketException e) {
    452                 Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
    453                 mRunning = false;
    454             }
    455 
    456             while (mRunning) {
    457                 final ByteBuffer byteBuffer;
    458                 try {
    459                     byteBuffer = recvKernelReply();
    460                 } catch (ErrnoException e) {
    461                     if (mRunning) { Log.w(TAG, "ErrnoException: ", e); }
    462                     break;
    463                 }
    464                 final long whenMs = SystemClock.elapsedRealtime();
    465                 if (byteBuffer == null) {
    466                     continue;
    467                 }
    468                 parseNetlinkMessageBuffer(byteBuffer, whenMs);
    469             }
    470 
    471             clearNetlinkSocket();
    472 
    473             mRunning = false; // Not a no-op when ErrnoException happened.
    474             if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
    475         }
    476 
    477         private void clearNetlinkSocket() {
    478             if (mSocket != null) {
    479                 mSocket.close();
    480             }
    481         }
    482 
    483             // TODO: Refactor the main loop to recreate the socket upon recoverable errors.
    484         private void setupNetlinkSocket() throws ErrnoException, SocketException {
    485             clearNetlinkSocket();
    486             mSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
    487 
    488             final NetlinkSocketAddress listenAddr = new NetlinkSocketAddress(
    489                     0, OsConstants.RTMGRP_NEIGH);
    490             mSocket.bind(listenAddr);
    491 
    492             if (VDBG) {
    493                 final NetlinkSocketAddress nlAddr = mSocket.getLocalAddress();
    494                 Log.d(TAG, "bound to sockaddr_nl{"
    495                         + ((long) (nlAddr.getPortId() & 0xffffffff)) + ", "
    496                         + nlAddr.getGroupsMask()
    497                         + "}");
    498             }
    499         }
    500 
    501         private ByteBuffer recvKernelReply() throws ErrnoException {
    502             try {
    503                 return mSocket.recvMessage(0);
    504             } catch (InterruptedIOException e) {
    505                 // Interruption or other error, e.g. another thread closed our file descriptor.
    506             } catch (ErrnoException e) {
    507                 if (e.errno != OsConstants.EAGAIN) {
    508                     throw e;
    509                 }
    510             }
    511             return null;
    512         }
    513 
    514         private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
    515             while (byteBuffer.remaining() > 0) {
    516                 final int position = byteBuffer.position();
    517                 final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
    518                 if (nlMsg == null || nlMsg.getHeader() == null) {
    519                     byteBuffer.position(position);
    520                     Log.e(TAG, "unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
    521                     break;
    522                 }
    523 
    524                 final int srcPortId = nlMsg.getHeader().nlmsg_pid;
    525                 if (srcPortId !=  0) {
    526                     Log.e(TAG, "non-kernel source portId: " + ((long) (srcPortId & 0xffffffff)));
    527                     break;
    528                 }
    529 
    530                 if (nlMsg instanceof NetlinkErrorMessage) {
    531                     Log.e(TAG, "netlink error: " + nlMsg);
    532                     continue;
    533                 } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
    534                     if (DBG) {
    535                         Log.d(TAG, "non-rtnetlink neighbor msg: " + nlMsg);
    536                     }
    537                     continue;
    538                 }
    539 
    540                 evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
    541             }
    542         }
    543 
    544         private void evaluateRtNetlinkNeighborMessage(
    545                 RtNetlinkNeighborMessage neighMsg, long whenMs) {
    546             final StructNdMsg ndMsg = neighMsg.getNdHeader();
    547             if (ndMsg == null || ndMsg.ndm_ifindex != mInterfaceIndex) {
    548                 return;
    549             }
    550 
    551             final InetAddress destination = neighMsg.getDestination();
    552             if (!isWatching(destination)) {
    553                 return;
    554             }
    555 
    556             final short msgType = neighMsg.getHeader().nlmsg_type;
    557             final short nudState = ndMsg.ndm_state;
    558             final String eventMsg = "NeighborEvent{"
    559                     + "elapsedMs=" + whenMs + ", "
    560                     + destination.getHostAddress() + ", "
    561                     + "[" + NetlinkConstants.hexify(neighMsg.getLinkLayerAddress()) + "], "
    562                     + NetlinkConstants.stringForNlMsgType(msgType) + ", "
    563                     + StructNdMsg.stringForNudState(nudState)
    564                     + "}";
    565 
    566             if (VDBG) {
    567                 Log.d(TAG, neighMsg.toString());
    568             } else if (DBG) {
    569                 Log.d(TAG, eventMsg);
    570             }
    571 
    572             synchronized (mLock) {
    573                 if (mIpWatchList.containsKey(destination)) {
    574                     final short value =
    575                             (msgType == NetlinkConstants.RTM_DELNEIGH)
    576                             ? StructNdMsg.NUD_NONE
    577                             : nudState;
    578                     mIpWatchList.put(destination, value);
    579                 }
    580             }
    581 
    582             if (nudState == StructNdMsg.NUD_FAILED) {
    583                 Log.w(TAG, "ALERT: " + eventMsg);
    584                 handleNeighborLost(eventMsg);
    585             }
    586         }
    587     }
    588 }
    589