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