| /* |
| * 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 android.content.Context; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.LinkProperties.ProvisioningChange; |
| import android.net.ProxyInfo; |
| import android.net.RouteInfo; |
| import android.net.ip.IpNeighborMonitor.NeighborEvent; |
| import android.net.metrics.IpConnectivityLog; |
| import android.net.metrics.IpReachabilityEvent; |
| import android.net.netlink.StructNdMsg; |
| import android.net.util.InterfaceParams; |
| import android.net.util.MultinetworkPolicyTracker; |
| import android.net.util.SharedLog; |
| import android.os.Handler; |
| import android.os.PowerManager; |
| import android.os.PowerManager.WakeLock; |
| import android.os.SystemClock; |
| import android.system.ErrnoException; |
| import android.system.OsConstants; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.DumpUtils; |
| import com.android.internal.util.DumpUtils.Dump; |
| |
| import java.io.InterruptedIOException; |
| import java.io.PrintWriter; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.SocketAddress; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| |
| /** |
| * 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 = false; |
| private static final boolean VDBG = false; |
| |
| 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); |
| } |
| |
| /** |
| * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing. |
| * TODO: consider also wrapping MultinetworkPolicyTracker in this interface. |
| */ |
| interface Dependencies { |
| void acquireWakeLock(long durationMs); |
| |
| 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); |
| } |
| }; |
| } |
| } |
| |
| private final InterfaceParams mInterfaceParams; |
| private final IpNeighborMonitor mIpNeighborMonitor; |
| private final SharedLog mLog; |
| private final Callback mCallback; |
| private final Dependencies mDependencies; |
| private final MultinetworkPolicyTracker mMultinetworkPolicyTracker; |
| private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); |
| 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; |
| |
| public IpReachabilityMonitor( |
| Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback, |
| MultinetworkPolicyTracker tracker) { |
| this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name)); |
| } |
| |
| @VisibleForTesting |
| IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback, |
| MultinetworkPolicyTracker tracker, Dependencies dependencies) { |
| if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams"); |
| |
| mInterfaceParams = ifParams; |
| mLog = log.forSubComponent(TAG); |
| mCallback = callback; |
| mMultinetworkPolicyTracker = tracker; |
| mDependencies = dependencies; |
| |
| mIpNeighborMonitor = new IpNeighborMonitor(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) { |
| mLog.w("ALERT neighbor went from: " + prev + " to: " + event); |
| handleNeighborLost(event); |
| } |
| }); |
| mIpNeighborMonitor.start(); |
| } |
| |
| public void stop() { |
| mIpNeighborMonitor.stop(); |
| clearLinkProperties(); |
| } |
| |
| public void dump(PrintWriter pw) { |
| DumpUtils.dumpAsync( |
| mIpNeighborMonitor.getHandler(), |
| new Dump() { |
| @Override |
| public void dump(PrintWriter pw, String prefix) { |
| pw.println(describeWatchList("\n")); |
| } |
| }, |
| pw, "", 1000); |
| } |
| |
| 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; |
| } |
| |
| 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 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). |
| if (entry.getValue().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 ProvisioningChange delta = LinkProperties.compareProvisioning( |
| mLinkProperties, whatIfLp); |
| |
| if (delta == ProvisioningChange.LOST_PROVISIONING) { |
| final String logMsg = "FAILURE: LOST_PROVISIONING, " + event; |
| Log.w(TAG, logMsg); |
| if (mCallback != null) { |
| // TODO: remove |ip| when the callback signature no longer has |
| // an InetAddress argument. |
| mCallback.notifyLost(ip, logMsg); |
| } |
| } |
| logNudFailed(delta); |
| } |
| |
| private boolean avoidingBadLinks() { |
| return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi(); |
| } |
| |
| public void probeAll() { |
| 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(); |
| } |
| |
| private static long getProbeWakeLockDuration() { |
| // Ideally, this would be computed by examining the values of: |
| // |
| // /proc/sys/net/ipv[46]/neigh/<ifname>/ucast_solicit |
| // |
| // and: |
| // |
| // /proc/sys/net/ipv[46]/neigh/<ifname>/retrans_time_ms |
| // |
| // For now, just make some assumptions. |
| final long numUnicastProbes = 3; |
| final long retransTimeMs = 1000; |
| final long gracePeriodMs = 500; |
| return (numUnicastProbes * retransTimeMs) + gracePeriodMs; |
| } |
| |
| private void logEvent(int probeType, int errorCode) { |
| int eventType = probeType | (errorCode & 0xff); |
| mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType)); |
| } |
| |
| private void logNudFailed(ProvisioningChange delta) { |
| long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs; |
| boolean isFromProbe = (duration < getProbeWakeLockDuration()); |
| boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING); |
| int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost); |
| mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType)); |
| } |
| } |