Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1eebaeb authored by Erik Kline's avatar Erik Kline
Browse files

Notify only on loss of provisioning.

Lots of code refactoring, include:
    - no longer watch for on-link proxies (only routers and DNS servers)
    - keep track of NUD state of neighbors of interest

Bug: 18581716
Change-Id: Ia7dbef0690daf54f69ffecefc14e1224fd402397
parent cd7ed16f
Loading
Loading
Loading
Loading
+125 −87
Original line number Original line Diff line number Diff line
@@ -16,8 +16,11 @@


package android.net;
package android.net;


import com.android.internal.annotations.GuardedBy;

import android.net.LinkAddress;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LinkProperties;
import android.net.LinkProperties.ProvisioningChange;
import android.net.ProxyInfo;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.RouteInfo;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkConstants;
@@ -32,7 +35,6 @@ import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.NetlinkSocketAddress;
import android.system.OsConstants;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;


import java.io.InterruptedIOException;
import java.io.InterruptedIOException;
@@ -46,6 +48,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Set;




@@ -63,6 +66,10 @@ public class IpReachabilityMonitor {
    private static final boolean VDBG = false;
    private static final boolean VDBG = false;


    public interface Callback {
    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).
        public void notifyLost(InetAddress ip, String logMsg);
        public void notifyLost(InetAddress ip, String logMsg);
    }
    }


@@ -70,11 +77,18 @@ public class IpReachabilityMonitor {
    private final String mInterfaceName;
    private final String mInterfaceName;
    private final int mInterfaceIndex;
    private final int mInterfaceIndex;
    private final Callback mCallback;
    private final Callback mCallback;
    private final Set<InetAddress> mIpWatchList;
    private int mIpWatchListVersion;
    private boolean mRunning;
    private final NetlinkSocketObserver mNetlinkSocketObserver;
    private final NetlinkSocketObserver mNetlinkSocketObserver;
    private final Thread mObserverThread;
    private final Thread mObserverThread;
    @GuardedBy("mLock")
    private LinkProperties mLinkProperties = new LinkProperties();
    // TODO: consider a map to a private NeighborState class holding more
    // information than a single NUD state entry.
    @GuardedBy("mLock")
    private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
    @GuardedBy("mLock")
    private int mIpWatchListVersion;
    @GuardedBy("mLock")
    private boolean mRunning;


    /**
    /**
     * Make the kernel to perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
     * Make the kernel to perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
@@ -84,21 +98,20 @@ public class IpReachabilityMonitor {
     */
     */
    public static boolean probeNeighbor(int ifIndex, InetAddress ip) {
    public static boolean probeNeighbor(int ifIndex, InetAddress ip) {
        final long IO_TIMEOUT = 300L;
        final long IO_TIMEOUT = 300L;
        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
        // This currently does not cause neighbor probing if the target |ip|
        // This currently does not cause neighbor probing if the target |ip|
        // has been confirmed reachable within the past "delay_probe_time"
        // has been confirmed reachable within the past "delay_probe_time"
        // seconds, i.e. within the past 5 seconds.
        // seconds, i.e. within the past 5 seconds.
        //
        //
        // TODO: replace with a transition directly to NUD_PROBE state once
        // TODO: replace with a transition directly to NUD_PROBE state once
        // kernels are updated to do so correctly.
        // kernels are updated to do so correctly.
        if (DBG) { Log.d(TAG, "Probing ip=" + ip.getHostAddress()); }
        if (DBG) { Log.d(TAG, msgSnippet); }


        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
                1, ip, StructNdMsg.NUD_DELAY, ifIndex, null);
                1, ip, StructNdMsg.NUD_DELAY, ifIndex, null);
        NetlinkSocket nlSocket = null;
        boolean returnValue = false;
        boolean returnValue = false;


        try {
        try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) {
            nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
            nlSocket.connectToKernel();
            nlSocket.connectToKernel();
            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
@@ -115,18 +128,17 @@ public class IpReachabilityMonitor {
                    bytes.position(0);
                    bytes.position(0);
                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
                } else {
                } else {
                    // TODO: consider ignoring EINVAL (-22), which appears to be
                    // normal when probing a neighbor for which the kernel does
                    // not already have / no longer has a link layer address.
                    errmsg = response.toString();
                    errmsg = response.toString();
                }
                }
                Log.e(TAG, "Error probing ip=" + ip.getHostAddress() +
                Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + errmsg);
                        ", errmsg=" + errmsg);
            }
            }
        } catch (ErrnoException | InterruptedIOException | SocketException e) {
        } catch (ErrnoException | InterruptedIOException | SocketException e) {
            Log.d(TAG, "Error probing ip=" + ip.getHostAddress(), e);
            Log.d(TAG, "Error " + msgSnippet, e);
        }
        }


        if (nlSocket != null) {
            nlSocket.close();
        }
        return returnValue;
        return returnValue;
    }
    }


@@ -140,9 +152,6 @@ public class IpReachabilityMonitor {
            throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e);
            throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e);
        }
        }
        mCallback = callback;
        mCallback = callback;
        mIpWatchList = new HashSet<InetAddress>();
        mIpWatchListVersion = 0;
        mRunning = false;
        mNetlinkSocketObserver = new NetlinkSocketObserver();
        mNetlinkSocketObserver = new NetlinkSocketObserver();
        mObserverThread = new Thread(mNetlinkSocketObserver);
        mObserverThread = new Thread(mNetlinkSocketObserver);
        mObserverThread.start();
        mObserverThread.start();
@@ -156,85 +165,85 @@ public class IpReachabilityMonitor {


    // TODO: add a public dump() method that can be called during a bug report.
    // TODO: add a public dump() method that can be called during a bug report.


    private static Set<InetAddress> getOnLinkNeighbors(LinkProperties lp) {
    private String describeWatchList() {
        Set<InetAddress> allIps = new HashSet<InetAddress>();
        final String delimiter = ", ";

        StringBuilder sb = new StringBuilder();
        final List<RouteInfo> routes = lp.getRoutes();
        synchronized (mLock) {
        for (RouteInfo route : routes) {
            sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, ");
            if (route.hasGateway()) {
            sb.append("v{" + mIpWatchListVersion + "}, ");
                allIps.add(route.getGateway());
            sb.append("ntable=[");
            boolean firstTime = true;
            for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
                if (firstTime) {
                    firstTime = false;
                } else {
                    sb.append(delimiter);
                }
                }
                sb.append(entry.getKey().getHostAddress() + "/" +
                        StructNdMsg.stringForNudState(entry.getValue()));
            }
            }

            sb.append("]");
        for (InetAddress nameserver : lp.getDnsServers()) {
            allIps.add(nameserver);
        }
        }

        return sb.toString();
        try {
            // Don't block here for DNS lookups.  If the proxy happens to be an
            // IP literal then we add it the list, but otherwise skip it.
            allIps.add(NetworkUtils.numericToInetAddress(lp.getHttpProxy().getHost()));
        } catch (NullPointerException|IllegalArgumentException e) {
            // No proxy, PAC proxy, or proxy is not a literal IP address.
    }
    }


        Set<InetAddress> neighbors = new HashSet<InetAddress>();
    private boolean isWatching(InetAddress ip) {
        for (InetAddress ip : allIps) {
        synchronized (mLock) {
            // TODO: consider using the prefixes of the LinkAddresses instead
            return mRunning && mIpWatchList.containsKey(ip);
            // of the routes--it may be more accurate.
            for (RouteInfo route : routes) {
                if (route.hasGateway()) {
                    continue;  // Not directly connected.
                }
                if (route.matches(ip)) {
                    neighbors.add(ip);
                    break;
                }
            }
        }
        }
        return neighbors;
    }
    }


    private String describeWatchList() {
    private boolean stillRunning() {
        synchronized (mLock) {
        synchronized (mLock) {
            return "version{" + mIpWatchListVersion + "}, " +
            return mRunning;
                    "ips=[" + TextUtils.join(",", mIpWatchList) + "]";
        }
        }
    }
    }


    private boolean isWatching(InetAddress ip) {
    private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
        synchronized (mLock) {
        for (RouteInfo route : routes) {
            return mRunning && mIpWatchList.contains(ip);
            if (!route.hasGateway() && route.matches(ip)) {
                return true;
            }
        }
        }
        return false;
    }
    }


    private boolean stillRunning() {
    private short getNeighborStateLocked(InetAddress ip) {
        synchronized (mLock) {
        if (mIpWatchList.containsKey(ip)) {
            return mRunning;
            return mIpWatchList.get(ip);
        }
        }
        return StructNdMsg.NUD_NONE;
    }
    }


    public void updateLinkProperties(LinkProperties lp) {
    public void updateLinkProperties(LinkProperties lp) {
        if (!mInterfaceName.equals(lp.getInterfaceName())) {
        if (!mInterfaceName.equals(lp.getInterfaceName())) {
            // TODO: figure out how to cope with interface changes.
            // TODO: figure out whether / how to cope with interface changes.
            Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
            Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
                    "' does not match: " + mInterfaceName);
                    "' does not match: " + mInterfaceName);
            return;
            return;
        }
        }


        // We rely upon the caller to determine when LinkProperties have actually
        synchronized (mLock) {
        // changed and call this at the appropriate time.  Note that even though
            mLinkProperties = new LinkProperties(lp);
        // the LinkProperties may change, the set of on-link neighbors might not.
            Map<InetAddress, Short> newIpWatchList = new HashMap<>();
        //

        // Nevertheless, just clear and re-add everything.
            final List<RouteInfo> routes = mLinkProperties.getRoutes();
        final Set<InetAddress> neighbors = getOnLinkNeighbors(lp);
            for (RouteInfo route : routes) {
        if (neighbors.isEmpty()) {
                if (route.hasGateway()) {
            return;
                    InetAddress gw = route.getGateway();
                    if (isOnLink(routes, gw)) {
                        newIpWatchList.put(gw, getNeighborStateLocked(gw));
                    }
                }
            }
            }


        synchronized (mLock) {
            for (InetAddress nameserver : lp.getDnsServers()) {
            mIpWatchList.clear();
                if (isOnLink(routes, nameserver)) {
            mIpWatchList.addAll(neighbors);
                    newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));
                }
            }

            mIpWatchList = newIpWatchList;
            mIpWatchListVersion++;
            mIpWatchListVersion++;
        }
        }
        if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
        if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
@@ -242,32 +251,51 @@ public class IpReachabilityMonitor {


    public void clearLinkProperties() {
    public void clearLinkProperties() {
        synchronized (mLock) {
        synchronized (mLock) {
            mLinkProperties.clear();
            mIpWatchList.clear();
            mIpWatchList.clear();
            mIpWatchListVersion++;
            mIpWatchListVersion++;
        }
        }
        if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
        if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
    }
    }


    private void notifyLost(InetAddress ip, String msg) {
    private void handleNeighborLost(String msg) {
        if (!isWatching(ip)) {
        InetAddress ip = null;
            // Ignore stray notifications.  This can happen when, for example,
        ProvisioningChange delta;
            // several neighbors are reported unreachable or deleted
        synchronized (mLock) {
            // back-to-back.  Because these messages are parsed serially, and
            LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
            // this method is called for each notification, the caller above us

            // may have already processed an earlier lost notification and
            for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
            // cleared the watch list as it moves to handle the situation.
                if (entry.getValue() != StructNdMsg.NUD_FAILED) {
            return;
                    continue;
                }

                ip = entry.getKey();
                for (RouteInfo route : mLinkProperties.getRoutes()) {
                    if (ip.equals(route.getGateway())) {
                        whatIfLp.removeRoute(route);
                    }
                }
                whatIfLp.removeDnsServer(ip);
            }
            }
        Log.w(TAG, "ALERT: " + ip.getHostAddress() + " -- " + msg);

            delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
        }

        if (delta == ProvisioningChange.LOST_PROVISIONING) {
            final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
            Log.w(TAG, logMsg);
            if (mCallback != null) {
            if (mCallback != null) {
            mCallback.notifyLost(ip, msg);
                // TODO: remove |ip| when the callback signature no longer has
                // an InetAddress argument.
                mCallback.notifyLost(ip, logMsg);
            }
        }
        }
    }
    }


    public void probeAll() {
    public void probeAll() {
        Set<InetAddress> ipProbeList = new HashSet<InetAddress>();
        Set<InetAddress> ipProbeList = new HashSet<InetAddress>();
        synchronized (mLock) {
        synchronized (mLock) {
            ipProbeList.addAll(mIpWatchList);
            ipProbeList.addAll(mIpWatchList.keySet());
        }
        }
        for (InetAddress target : ipProbeList) {
        for (InetAddress target : ipProbeList) {
            if (!stillRunning()) {
            if (!stillRunning()) {
@@ -411,10 +439,20 @@ public class IpReachabilityMonitor {
                Log.d(TAG, eventMsg);
                Log.d(TAG, eventMsg);
            }
            }


            synchronized (mLock) {
                if (mIpWatchList.containsKey(destination)) {
                    final short value =
                            (msgType == NetlinkConstants.RTM_DELNEIGH)
                            ? StructNdMsg.NUD_FAILED
                            : nudState;
                    mIpWatchList.put(destination, value);
                }
            }

            if ((msgType == NetlinkConstants.RTM_DELNEIGH) ||
            if ((msgType == NetlinkConstants.RTM_DELNEIGH) ||
                (nudState == StructNdMsg.NUD_FAILED)) {
                (nudState == StructNdMsg.NUD_FAILED)) {
                final String logMsg = "FAILURE: " + eventMsg;
                Log.w(TAG, "ALERT: " + eventMsg);
                notifyLost(destination, logMsg);
                handleNeighborLost(eventMsg);
            }
            }
        }
        }
    }
    }
+2 −0
Original line number Original line Diff line number Diff line
@@ -33,6 +33,7 @@ public class StructNdMsg {
    public static final int STRUCT_SIZE = 12;
    public static final int STRUCT_SIZE = 12;


    // Neighbor Cache Entry States
    // Neighbor Cache Entry States
    public static final short NUD_NONE        = 0x00;
    public static final short NUD_INCOMPLETE  = 0x01;
    public static final short NUD_INCOMPLETE  = 0x01;
    public static final short NUD_REACHABLE   = 0x02;
    public static final short NUD_REACHABLE   = 0x02;
    public static final short NUD_STALE       = 0x04;
    public static final short NUD_STALE       = 0x04;
@@ -44,6 +45,7 @@ public class StructNdMsg {


    public static String stringForNudState(short nudState) {
    public static String stringForNudState(short nudState) {
        switch (nudState) {
        switch (nudState) {
            case NUD_NONE: return "NUD_NONE";
            case NUD_INCOMPLETE: return "NUD_INCOMPLETE";
            case NUD_INCOMPLETE: return "NUD_INCOMPLETE";
            case NUD_REACHABLE: return "NUD_REACHABLE";
            case NUD_REACHABLE: return "NUD_REACHABLE";
            case NUD_STALE: return "NUD_STALE";
            case NUD_STALE: return "NUD_STALE";