Home | History | Annotate | Download | only in net
      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;
     18 
     19 import com.android.internal.annotations.GuardedBy;
     20 
     21 import android.net.LinkAddress;
     22 import android.net.LinkProperties;
     23 import android.net.LinkProperties.ProvisioningChange;
     24 import android.net.ProxyInfo;
     25 import android.net.RouteInfo;
     26 import android.net.netlink.NetlinkConstants;
     27 import android.net.netlink.NetlinkErrorMessage;
     28 import android.net.netlink.NetlinkMessage;
     29 import android.net.netlink.NetlinkSocket;
     30 import android.net.netlink.RtNetlinkNeighborMessage;
     31 import android.net.netlink.StructNdaCacheInfo;
     32 import android.net.netlink.StructNdMsg;
     33 import android.net.netlink.StructNlMsgHdr;
     34 import android.os.SystemClock;
     35 import android.system.ErrnoException;
     36 import android.system.NetlinkSocketAddress;
     37 import android.system.OsConstants;
     38 import android.util.Log;
     39 
     40 import java.io.InterruptedIOException;
     41 import java.net.InetAddress;
     42 import java.net.InetSocketAddress;
     43 import java.net.NetworkInterface;
     44 import java.net.SocketAddress;
     45 import java.net.SocketException;
     46 import java.nio.ByteBuffer;
     47 import java.util.Arrays;
     48 import java.util.HashMap;
     49 import java.util.HashSet;
     50 import java.util.List;
     51 import java.util.Map;
     52 import java.util.Set;
     53 
     54 
     55 /**
     56  * IpReachabilityMonitor.
     57  *
     58  * Monitors on-link IP reachability and notifies callers whenever any on-link
     59  * addresses of interest appear to have become unresponsive.
     60  *
     61  * @hide
     62  */
     63 public class IpReachabilityMonitor {
     64     private static final String TAG = "IpReachabilityMonitor";
     65     private static final boolean DBG = true;
     66     private static final boolean VDBG = false;
     67 
     68     public interface Callback {
     69         // This callback function must execute as quickly as possible as it is
     70         // run on the same thread that listens to kernel neighbor updates.
     71         //
     72         // TODO: refactor to something like notifyProvisioningLost(String msg).
     73         public void notifyLost(InetAddress ip, String logMsg);
     74     }
     75 
     76     private final Object mLock = new Object();
     77     private final String mInterfaceName;
     78     private final int mInterfaceIndex;
     79     private final Callback mCallback;
     80     private final NetlinkSocketObserver mNetlinkSocketObserver;
     81     private final Thread mObserverThread;
     82     @GuardedBy("mLock")
     83     private LinkProperties mLinkProperties = new LinkProperties();
     84     // TODO: consider a map to a private NeighborState class holding more
     85     // information than a single NUD state entry.
     86     @GuardedBy("mLock")
     87     private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
     88     @GuardedBy("mLock")
     89     private int mIpWatchListVersion;
     90     @GuardedBy("mLock")
     91     private boolean mRunning;
     92 
     93     /**
     94      * Make the kernel to perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
     95      * for the given IP address on the specified interface index.
     96      *
     97      * @return true, if the request was successfully passed to the kernel; false otherwise.
     98      */
     99     public static boolean probeNeighbor(int ifIndex, InetAddress ip) {
    100         final long IO_TIMEOUT = 300L;
    101         final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
    102         if (DBG) { Log.d(TAG, msgSnippet); }
    103 
    104         final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
    105                 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
    106         boolean returnValue = false;
    107 
    108         try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) {
    109             nlSocket.connectToKernel();
    110             nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
    111             final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
    112             final NetlinkMessage response = NetlinkMessage.parse(bytes);
    113             if (response != null && response instanceof NetlinkErrorMessage &&
    114                     (((NetlinkErrorMessage) response).getNlMsgError() != null) &&
    115                     (((NetlinkErrorMessage) response).getNlMsgError().error == 0)) {
    116                 returnValue = true;
    117             } else {
    118                 String errmsg;
    119                 if (bytes == null) {
    120                     errmsg = "null recvMessage";
    121                 } else if (response == null) {
    122                     bytes.position(0);
    123                     errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
    124                 } else {
    125                     // TODO: consider ignoring EINVAL (-22), which appears to be
    126                     // normal when probing a neighbor for which the kernel does
    127                     // not already have / no longer has a link layer address.
    128                     errmsg = response.toString();
    129                 }
    130                 Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + errmsg);
    131             }
    132         } catch (ErrnoException | InterruptedIOException | SocketException e) {
    133             Log.d(TAG, "Error " + msgSnippet, e);
    134         }
    135 
    136         return returnValue;
    137     }
    138 
    139     public IpReachabilityMonitor(String ifName, Callback callback) throws IllegalArgumentException {
    140         mInterfaceName = ifName;
    141         int ifIndex = -1;
    142         try {
    143             NetworkInterface netIf = NetworkInterface.getByName(ifName);
    144             mInterfaceIndex = netIf.getIndex();
    145         } catch (SocketException | NullPointerException e) {
    146             throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e);
    147         }
    148         mCallback = callback;
    149         mNetlinkSocketObserver = new NetlinkSocketObserver();
    150         mObserverThread = new Thread(mNetlinkSocketObserver);
    151         mObserverThread.start();
    152     }
    153 
    154     public void stop() {
    155         synchronized (mLock) { mRunning = false; }
    156         clearLinkProperties();
    157         mNetlinkSocketObserver.clearNetlinkSocket();
    158     }
    159 
    160     // TODO: add a public dump() method that can be called during a bug report.
    161 
    162     private String describeWatchList() {
    163         final String delimiter = ", ";
    164         StringBuilder sb = new StringBuilder();
    165         synchronized (mLock) {
    166             sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, ");
    167             sb.append("v{" + mIpWatchListVersion + "}, ");
    168             sb.append("ntable=[");
    169             boolean firstTime = true;
    170             for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
    171                 if (firstTime) {
    172                     firstTime = false;
    173                 } else {
    174                     sb.append(delimiter);
    175                 }
    176                 sb.append(entry.getKey().getHostAddress() + "/" +
    177                         StructNdMsg.stringForNudState(entry.getValue()));
    178             }
    179             sb.append("]");
    180         }
    181         return sb.toString();
    182     }
    183 
    184     private boolean isWatching(InetAddress ip) {
    185         synchronized (mLock) {
    186             return mRunning && mIpWatchList.containsKey(ip);
    187         }
    188     }
    189 
    190     private boolean stillRunning() {
    191         synchronized (mLock) {
    192             return mRunning;
    193         }
    194     }
    195 
    196     private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
    197         for (RouteInfo route : routes) {
    198             if (!route.hasGateway() && route.matches(ip)) {
    199                 return true;
    200             }
    201         }
    202         return false;
    203     }
    204 
    205     private short getNeighborStateLocked(InetAddress ip) {
    206         if (mIpWatchList.containsKey(ip)) {
    207             return mIpWatchList.get(ip);
    208         }
    209         return StructNdMsg.NUD_NONE;
    210     }
    211 
    212     public void updateLinkProperties(LinkProperties lp) {
    213         if (!mInterfaceName.equals(lp.getInterfaceName())) {
    214             // TODO: figure out whether / how to cope with interface changes.
    215             Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
    216                     "' does not match: " + mInterfaceName);
    217             return;
    218         }
    219 
    220         synchronized (mLock) {
    221             mLinkProperties = new LinkProperties(lp);
    222             Map<InetAddress, Short> newIpWatchList = new HashMap<>();
    223 
    224             final List<RouteInfo> routes = mLinkProperties.getRoutes();
    225             for (RouteInfo route : routes) {
    226                 if (route.hasGateway()) {
    227                     InetAddress gw = route.getGateway();
    228                     if (isOnLink(routes, gw)) {
    229                         newIpWatchList.put(gw, getNeighborStateLocked(gw));
    230                     }
    231                 }
    232             }
    233 
    234             for (InetAddress nameserver : lp.getDnsServers()) {
    235                 if (isOnLink(routes, nameserver)) {
    236                     newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));
    237                 }
    238             }
    239 
    240             mIpWatchList = newIpWatchList;
    241             mIpWatchListVersion++;
    242         }
    243         if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
    244     }
    245 
    246     public void clearLinkProperties() {
    247         synchronized (mLock) {
    248             mLinkProperties.clear();
    249             mIpWatchList.clear();
    250             mIpWatchListVersion++;
    251         }
    252         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
    253     }
    254 
    255     private void handleNeighborLost(String msg) {
    256         InetAddress ip = null;
    257         ProvisioningChange delta;
    258         synchronized (mLock) {
    259             LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
    260 
    261             for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
    262                 if (entry.getValue() != StructNdMsg.NUD_FAILED) {
    263                     continue;
    264                 }
    265 
    266                 ip = entry.getKey();
    267                 for (RouteInfo route : mLinkProperties.getRoutes()) {
    268                     if (ip.equals(route.getGateway())) {
    269                         whatIfLp.removeRoute(route);
    270                     }
    271                 }
    272                 whatIfLp.removeDnsServer(ip);
    273             }
    274 
    275             delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
    276         }
    277 
    278         if (delta == ProvisioningChange.LOST_PROVISIONING) {
    279             final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
    280             Log.w(TAG, logMsg);
    281             if (mCallback != null) {
    282                 // TODO: remove |ip| when the callback signature no longer has
    283                 // an InetAddress argument.
    284                 mCallback.notifyLost(ip, logMsg);
    285             }
    286         }
    287     }
    288 
    289     public void probeAll() {
    290         Set<InetAddress> ipProbeList = new HashSet<InetAddress>();
    291         synchronized (mLock) {
    292             ipProbeList.addAll(mIpWatchList.keySet());
    293         }
    294         for (InetAddress target : ipProbeList) {
    295             if (!stillRunning()) {
    296                 break;
    297             }
    298             probeNeighbor(mInterfaceIndex, target);
    299         }
    300     }
    301 
    302 
    303     // TODO: simply the number of objects by making this extend Thread.
    304     private final class NetlinkSocketObserver implements Runnable {
    305         private static final String TAG = "NetlinkSocketObserver";
    306         private NetlinkSocket mSocket;
    307 
    308         @Override
    309         public void run() {
    310             if (VDBG) { Log.d(TAG, "Starting observing thread."); }
    311             synchronized (mLock) { mRunning = true; }
    312 
    313             try {
    314                 setupNetlinkSocket();
    315             } catch (ErrnoException | SocketException e) {
    316                 Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
    317                 synchronized (mLock) { mRunning = false; }
    318             }
    319 
    320             ByteBuffer byteBuffer;
    321             while (stillRunning()) {
    322                 try {
    323                     byteBuffer = recvKernelReply();
    324                 } catch (ErrnoException e) {
    325                     Log.w(TAG, "ErrnoException: ", e);
    326                     break;
    327                 }
    328                 final long whenMs = SystemClock.elapsedRealtime();
    329                 if (byteBuffer == null) {
    330                     continue;
    331                 }
    332                 parseNetlinkMessageBuffer(byteBuffer, whenMs);
    333             }
    334 
    335             clearNetlinkSocket();
    336 
    337             synchronized (mLock) { mRunning = false; }
    338             if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
    339         }
    340 
    341         private void clearNetlinkSocket() {
    342             if (mSocket != null) {
    343                 mSocket.close();
    344             }
    345         }
    346 
    347             // TODO: Refactor the main loop to recreate the socket upon recoverable errors.
    348         private void setupNetlinkSocket() throws ErrnoException, SocketException {
    349             clearNetlinkSocket();
    350             mSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
    351 
    352             final NetlinkSocketAddress listenAddr = new NetlinkSocketAddress(
    353                     0, OsConstants.RTMGRP_NEIGH);
    354             mSocket.bind(listenAddr);
    355 
    356             if (VDBG) {
    357                 final NetlinkSocketAddress nlAddr = mSocket.getLocalAddress();
    358                 Log.d(TAG, "bound to sockaddr_nl{"
    359                         + ((long) (nlAddr.getPortId() & 0xffffffff)) + ", "
    360                         + nlAddr.getGroupsMask()
    361                         + "}");
    362             }
    363         }
    364 
    365         private ByteBuffer recvKernelReply() throws ErrnoException {
    366             try {
    367                 return mSocket.recvMessage(0);
    368             } catch (InterruptedIOException e) {
    369                 // Interruption or other error, e.g. another thread closed our file descriptor.
    370             } catch (ErrnoException e) {
    371                 if (e.errno != OsConstants.EAGAIN) {
    372                     throw e;
    373                 }
    374             }
    375             return null;
    376         }
    377 
    378         private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
    379             while (byteBuffer.remaining() > 0) {
    380                 final int position = byteBuffer.position();
    381                 final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
    382                 if (nlMsg == null || nlMsg.getHeader() == null) {
    383                     byteBuffer.position(position);
    384                     Log.e(TAG, "unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
    385                     break;
    386                 }
    387 
    388                 final int srcPortId = nlMsg.getHeader().nlmsg_pid;
    389                 if (srcPortId !=  0) {
    390                     Log.e(TAG, "non-kernel source portId: " + ((long) (srcPortId & 0xffffffff)));
    391                     break;
    392                 }
    393 
    394                 if (nlMsg instanceof NetlinkErrorMessage) {
    395                     Log.e(TAG, "netlink error: " + nlMsg);
    396                     continue;
    397                 } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
    398                     if (DBG) {
    399                         Log.d(TAG, "non-rtnetlink neighbor msg: " + nlMsg);
    400                     }
    401                     continue;
    402                 }
    403 
    404                 evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
    405             }
    406         }
    407 
    408         private void evaluateRtNetlinkNeighborMessage(
    409                 RtNetlinkNeighborMessage neighMsg, long whenMs) {
    410             final StructNdMsg ndMsg = neighMsg.getNdHeader();
    411             if (ndMsg == null || ndMsg.ndm_ifindex != mInterfaceIndex) {
    412                 return;
    413             }
    414 
    415             final InetAddress destination = neighMsg.getDestination();
    416             if (!isWatching(destination)) {
    417                 return;
    418             }
    419 
    420             final short msgType = neighMsg.getHeader().nlmsg_type;
    421             final short nudState = ndMsg.ndm_state;
    422             final String eventMsg = "NeighborEvent{"
    423                     + "elapsedMs=" + whenMs + ", "
    424                     + destination.getHostAddress() + ", "
    425                     + "[" + NetlinkConstants.hexify(neighMsg.getLinkLayerAddress()) + "], "
    426                     + NetlinkConstants.stringForNlMsgType(msgType) + ", "
    427                     + StructNdMsg.stringForNudState(nudState)
    428                     + "}";
    429 
    430             if (VDBG) {
    431                 Log.d(TAG, neighMsg.toString());
    432             } else if (DBG) {
    433                 Log.d(TAG, eventMsg);
    434             }
    435 
    436             synchronized (mLock) {
    437                 if (mIpWatchList.containsKey(destination)) {
    438                     final short value =
    439                             (msgType == NetlinkConstants.RTM_DELNEIGH)
    440                             ? StructNdMsg.NUD_NONE
    441                             : nudState;
    442                     mIpWatchList.put(destination, value);
    443                 }
    444             }
    445 
    446             if (nudState == StructNdMsg.NUD_FAILED) {
    447                 Log.w(TAG, "ALERT: " + eventMsg);
    448                 handleNeighborLost(eventMsg);
    449             }
    450         }
    451     }
    452 }
    453