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