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
17package android.net;
18
19import com.android.internal.annotations.GuardedBy;
20
21import android.net.LinkAddress;
22import android.net.LinkProperties;
23import android.net.LinkProperties.ProvisioningChange;
24import android.net.ProxyInfo;
25import android.net.RouteInfo;
26import android.net.netlink.NetlinkConstants;
27import android.net.netlink.NetlinkErrorMessage;
28import android.net.netlink.NetlinkMessage;
29import android.net.netlink.NetlinkSocket;
30import android.net.netlink.RtNetlinkNeighborMessage;
31import android.net.netlink.StructNdaCacheInfo;
32import android.net.netlink.StructNdMsg;
33import android.net.netlink.StructNlMsgHdr;
34import android.os.SystemClock;
35import android.system.ErrnoException;
36import android.system.NetlinkSocketAddress;
37import android.system.OsConstants;
38import android.util.Log;
39
40import java.io.InterruptedIOException;
41import java.net.InetAddress;
42import java.net.InetSocketAddress;
43import java.net.NetworkInterface;
44import java.net.SocketAddress;
45import java.net.SocketException;
46import java.nio.ByteBuffer;
47import java.util.Arrays;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.List;
51import java.util.Map;
52import 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 */
63public 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