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 android.content.Context;
     20 import android.net.LinkAddress;
     21 import android.net.LinkProperties;
     22 import android.net.LinkProperties.ProvisioningChange;
     23 import android.net.ProxyInfo;
     24 import android.net.RouteInfo;
     25 import android.net.ip.IpNeighborMonitor.NeighborEvent;
     26 import android.net.metrics.IpConnectivityLog;
     27 import android.net.metrics.IpReachabilityEvent;
     28 import android.net.netlink.StructNdMsg;
     29 import android.net.util.InterfaceParams;
     30 import android.net.util.MultinetworkPolicyTracker;
     31 import android.net.util.SharedLog;
     32 import android.os.Handler;
     33 import android.os.PowerManager;
     34 import android.os.PowerManager.WakeLock;
     35 import android.os.SystemClock;
     36 import android.system.ErrnoException;
     37 import android.system.OsConstants;
     38 import android.util.Log;
     39 
     40 import com.android.internal.annotations.GuardedBy;
     41 import com.android.internal.annotations.VisibleForTesting;
     42 import com.android.internal.util.DumpUtils;
     43 import com.android.internal.util.DumpUtils.Dump;
     44 
     45 import java.io.InterruptedIOException;
     46 import java.io.PrintWriter;
     47 import java.net.Inet6Address;
     48 import java.net.InetAddress;
     49 import java.net.InetSocketAddress;
     50 import java.net.SocketAddress;
     51 import java.nio.ByteBuffer;
     52 import java.util.ArrayList;
     53 import java.util.Arrays;
     54 import java.util.HashMap;
     55 import java.util.List;
     56 import java.util.Map;
     57 import java.util.Set;
     58 
     59 
     60 /**
     61  * IpReachabilityMonitor.
     62  *
     63  * Monitors on-link IP reachability and notifies callers whenever any on-link
     64  * addresses of interest appear to have become unresponsive.
     65  *
     66  * This code does not concern itself with "why" a neighbour might have become
     67  * unreachable. Instead, it primarily reacts to the kernel's notion of IP
     68  * reachability for each of the neighbours we know to be critically important
     69  * to normal network connectivity. As such, it is often "just the messenger":
     70  * the neighbours about which it warns are already deemed by the kernel to have
     71  * become unreachable.
     72  *
     73  *
     74  * How it works:
     75  *
     76  *   1. The "on-link neighbours of interest" found in a given LinkProperties
     77  *      instance are added to a "watch list" via #updateLinkProperties().
     78  *      This usually means all default gateways and any on-link DNS servers.
     79  *
     80  *   2. We listen continuously for netlink neighbour messages (RTM_NEWNEIGH,
     81  *      RTM_DELNEIGH), watching only for neighbours in the watch list.
     82  *
     83  *        - A neighbour going into NUD_REACHABLE, NUD_STALE, NUD_DELAY, and
     84  *          even NUD_PROBE is perfectly normal; we merely record the new state.
     85  *
     86  *        - A neighbour's entry may be deleted (RTM_DELNEIGH), for example due
     87  *          to garbage collection.  This is not necessarily of immediate
     88  *          concern; we record the neighbour as moving to NUD_NONE.
     89  *
     90  *        - A neighbour transitioning to NUD_FAILED (for any reason) is
     91  *          critically important and is handled as described below in #4.
     92  *
     93  *   3. All on-link neighbours in the watch list can be forcibly "probed" by
     94  *      calling #probeAll(). This should be called whenever it is important to
     95  *      verify that critical neighbours on the link are still reachable, e.g.
     96  *      when roaming between BSSIDs.
     97  *
     98  *        - The kernel will send unicast ARP requests for IPv4 neighbours and
     99  *          unicast NS packets for IPv6 neighbours.  The expected replies will
    100  *          likely be unicast.
    101  *
    102  *        - The forced probing is done holding a wakelock. The kernel may,
    103  *          however, initiate probing of a neighbor on its own, i.e. whenever
    104  *          a neighbour has expired from NUD_DELAY.
    105  *
    106  *        - The kernel sends:
    107  *
    108  *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/ucast_solicit
    109  *
    110  *          number of probes (usually 3) every:
    111  *
    112  *              /proc/sys/net/ipv{4,6}/neigh/<ifname>/retrans_time_ms
    113  *
    114  *          number of milliseconds (usually 1000ms). This normally results in
    115  *          3 unicast packets, 1 per second.
    116  *
    117  *        - If no response is received to any of the probe packets, the kernel
    118  *          marks the neighbour as being in state NUD_FAILED, and the listening
    119  *          process in #2 will learn of it.
    120  *
    121  *   4. We call the supplied Callback#notifyLost() function if the loss of a
    122  *      neighbour in NUD_FAILED would cause IPv4 or IPv6 configuration to
    123  *      become incomplete (a loss of provisioning).
    124  *
    125  *        - For example, losing all our IPv4 on-link DNS servers (or losing
    126  *          our only IPv6 default gateway) constitutes a loss of IPv4 (IPv6)
    127  *          provisioning; Callback#notifyLost() would be called.
    128  *
    129  *        - Since it can be non-trivial to reacquire certain IP provisioning
    130  *          state it may be best for the link to disconnect completely and
    131  *          reconnect afresh.
    132  *
    133  * Accessing an instance of this class from multiple threads is NOT safe.
    134  *
    135  * @hide
    136  */
    137 public class IpReachabilityMonitor {
    138     private static final String TAG = "IpReachabilityMonitor";
    139     private static final boolean DBG = false;
    140     private static final boolean VDBG = false;
    141 
    142     public interface Callback {
    143         // This callback function must execute as quickly as possible as it is
    144         // run on the same thread that listens to kernel neighbor updates.
    145         //
    146         // TODO: refactor to something like notifyProvisioningLost(String msg).
    147         public void notifyLost(InetAddress ip, String logMsg);
    148     }
    149 
    150     /**
    151      * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing.
    152      * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
    153      */
    154     interface Dependencies {
    155         void acquireWakeLock(long durationMs);
    156 
    157         static Dependencies makeDefault(Context context, String iface) {
    158             final String lockName = TAG + "." + iface;
    159             final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    160             final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
    161 
    162             return new Dependencies() {
    163                 public void acquireWakeLock(long durationMs) {
    164                     lock.acquire(durationMs);
    165                 }
    166             };
    167         }
    168     }
    169 
    170     private final InterfaceParams mInterfaceParams;
    171     private final IpNeighborMonitor mIpNeighborMonitor;
    172     private final SharedLog mLog;
    173     private final Callback mCallback;
    174     private final Dependencies mDependencies;
    175     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
    176     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
    177     private LinkProperties mLinkProperties = new LinkProperties();
    178     private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
    179     // Time in milliseconds of the last forced probe request.
    180     private volatile long mLastProbeTimeMs;
    181 
    182     public IpReachabilityMonitor(
    183             Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
    184             MultinetworkPolicyTracker tracker) {
    185         this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name));
    186     }
    187 
    188     @VisibleForTesting
    189     IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
    190             MultinetworkPolicyTracker tracker, Dependencies dependencies) {
    191         if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
    192 
    193         mInterfaceParams = ifParams;
    194         mLog = log.forSubComponent(TAG);
    195         mCallback = callback;
    196         mMultinetworkPolicyTracker = tracker;
    197         mDependencies = dependencies;
    198 
    199         mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
    200                 (NeighborEvent event) -> {
    201                     if (mInterfaceParams.index != event.ifindex) return;
    202                     if (!mNeighborWatchList.containsKey(event.ip)) return;
    203 
    204                     final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
    205 
    206                     // TODO: Consider what to do with other states that are not within
    207                     // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
    208                     if (event.nudState == StructNdMsg.NUD_FAILED) {
    209                         mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
    210                         handleNeighborLost(event);
    211                     }
    212                 });
    213         mIpNeighborMonitor.start();
    214     }
    215 
    216     public void stop() {
    217         mIpNeighborMonitor.stop();
    218         clearLinkProperties();
    219     }
    220 
    221     public void dump(PrintWriter pw) {
    222         DumpUtils.dumpAsync(
    223                 mIpNeighborMonitor.getHandler(),
    224                 new Dump() {
    225                     @Override
    226                     public void dump(PrintWriter pw, String prefix) {
    227                         pw.println(describeWatchList("\n"));
    228                     }
    229                 },
    230                 pw, "", 1000);
    231     }
    232 
    233     private String describeWatchList() { return describeWatchList(" "); }
    234 
    235     private String describeWatchList(String sep) {
    236         final StringBuilder sb = new StringBuilder();
    237         sb.append("iface{" + mInterfaceParams + "}," + sep);
    238         sb.append("ntable=[" + sep);
    239         String delimiter = "";
    240         for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
    241             sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
    242             delimiter = "," + sep;
    243         }
    244         sb.append("]");
    245         return sb.toString();
    246     }
    247 
    248     private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
    249         for (RouteInfo route : routes) {
    250             if (!route.hasGateway() && route.matches(ip)) {
    251                 return true;
    252             }
    253         }
    254         return false;
    255     }
    256 
    257     public void updateLinkProperties(LinkProperties lp) {
    258         if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
    259             // TODO: figure out whether / how to cope with interface changes.
    260             Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
    261                     "' does not match: " + mInterfaceParams.name);
    262             return;
    263         }
    264 
    265         mLinkProperties = new LinkProperties(lp);
    266         Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
    267 
    268         final List<RouteInfo> routes = mLinkProperties.getRoutes();
    269         for (RouteInfo route : routes) {
    270             if (route.hasGateway()) {
    271                 InetAddress gw = route.getGateway();
    272                 if (isOnLink(routes, gw)) {
    273                     newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
    274                 }
    275             }
    276         }
    277 
    278         for (InetAddress dns : lp.getDnsServers()) {
    279             if (isOnLink(routes, dns)) {
    280                 newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
    281             }
    282         }
    283 
    284         mNeighborWatchList = newNeighborWatchList;
    285         if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
    286     }
    287 
    288     public void clearLinkProperties() {
    289         mLinkProperties.clear();
    290         mNeighborWatchList.clear();
    291         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
    292     }
    293 
    294     private void handleNeighborLost(NeighborEvent event) {
    295         final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
    296 
    297         InetAddress ip = null;
    298         for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
    299             // TODO: Consider using NeighborEvent#isValid() here; it's more
    300             // strict but may interact badly if other entries are somehow in
    301             // NUD_INCOMPLETE (say, during network attach).
    302             if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
    303 
    304             ip = entry.getKey();
    305             for (RouteInfo route : mLinkProperties.getRoutes()) {
    306                 if (ip.equals(route.getGateway())) {
    307                     whatIfLp.removeRoute(route);
    308                 }
    309             }
    310 
    311             if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
    312                 // We should do this unconditionally, but alas we cannot: b/31827713.
    313                 whatIfLp.removeDnsServer(ip);
    314             }
    315         }
    316 
    317         final ProvisioningChange delta = LinkProperties.compareProvisioning(
    318                 mLinkProperties, whatIfLp);
    319 
    320         if (delta == ProvisioningChange.LOST_PROVISIONING) {
    321             final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
    322             Log.w(TAG, logMsg);
    323             if (mCallback != null) {
    324                 // TODO: remove |ip| when the callback signature no longer has
    325                 // an InetAddress argument.
    326                 mCallback.notifyLost(ip, logMsg);
    327             }
    328         }
    329         logNudFailed(delta);
    330     }
    331 
    332     private boolean avoidingBadLinks() {
    333         return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi();
    334     }
    335 
    336     public void probeAll() {
    337         final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
    338 
    339         if (!ipProbeList.isEmpty()) {
    340             // Keep the CPU awake long enough to allow all ARP/ND
    341             // probes a reasonable chance at success. See b/23197666.
    342             //
    343             // The wakelock we use is (by default) refcounted, and this version
    344             // of acquire(timeout) queues a release message to keep acquisitions
    345             // and releases balanced.
    346             mDependencies.acquireWakeLock(getProbeWakeLockDuration());
    347         }
    348 
    349         for (InetAddress ip : ipProbeList) {
    350             final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
    351             mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
    352                      ip.getHostAddress(), rval));
    353             logEvent(IpReachabilityEvent.PROBE, rval);
    354         }
    355         mLastProbeTimeMs = SystemClock.elapsedRealtime();
    356     }
    357 
    358     private static long getProbeWakeLockDuration() {
    359         // Ideally, this would be computed by examining the values of:
    360         //
    361         //     /proc/sys/net/ipv[46]/neigh/<ifname>/ucast_solicit
    362         //
    363         // and:
    364         //
    365         //     /proc/sys/net/ipv[46]/neigh/<ifname>/retrans_time_ms
    366         //
    367         // For now, just make some assumptions.
    368         final long numUnicastProbes = 3;
    369         final long retransTimeMs = 1000;
    370         final long gracePeriodMs = 500;
    371         return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
    372     }
    373 
    374     private void logEvent(int probeType, int errorCode) {
    375         int eventType = probeType | (errorCode & 0xff);
    376         mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
    377     }
    378 
    379     private void logNudFailed(ProvisioningChange delta) {
    380         long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs;
    381         boolean isFromProbe = (duration < getProbeWakeLockDuration());
    382         boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
    383         int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
    384         mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
    385     }
    386 }
    387