Merge "Refactor to IpNeighborMonitor and single-threaded semantics"

This commit is contained in:
Treehugger Robot
2017-12-08 05:44:26 +00:00
committed by Gerrit Code Review
10 changed files with 431 additions and 439 deletions

View File

@@ -19,7 +19,7 @@ package android.net.ip;
import static android.system.OsConstants.*;
import android.net.NetworkUtils;
import android.net.util.BlockingSocketReader;
import android.net.util.PacketReader;
import android.net.util.ConnectivityPacketSummary;
import android.os.Handler;
import android.system.ErrnoException;
@@ -65,7 +65,7 @@ public class ConnectivityPacketTracker {
private final String mTag;
private final LocalLog mLog;
private final BlockingSocketReader mPacketListener;
private final PacketReader mPacketListener;
private boolean mRunning;
private String mDisplayName;
@@ -101,7 +101,7 @@ public class ConnectivityPacketTracker {
mDisplayName = null;
}
private final class PacketListener extends BlockingSocketReader {
private final class PacketListener extends PacketReader {
private final int mIfIndex;
private final byte mHwAddr[];

View File

@@ -815,6 +815,15 @@ public class IpClient extends StateMachine {
pw.println(Objects.toString(provisioningConfig, "N/A"));
pw.decreaseIndent();
final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
if (iprm != null) {
pw.println();
pw.println(mTag + " current IpReachabilityMonitor state:");
pw.increaseIndent();
iprm.dump(pw);
pw.decreaseIndent();
}
pw.println();
pw.println(mTag + " StateMachine dump:");
pw.increaseIndent();
@@ -1237,6 +1246,7 @@ public class IpClient extends StateMachine {
mIpReachabilityMonitor = new IpReachabilityMonitor(
mContext,
mInterfaceName,
getHandler(),
mLog,
new IpReachabilityMonitor.Callback() {
@Override

View File

@@ -0,0 +1,236 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.ip;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkErrorMessage;
import android.net.netlink.NetlinkMessage;
import android.net.netlink.NetlinkSocket;
import android.net.netlink.RtNetlinkNeighborMessage;
import android.net.netlink.StructNdMsg;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.PacketReader;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import com.android.internal.util.BitUtils;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.StringJoiner;
/**
* IpNeighborMonitor.
*
* Monitors the kernel rtnetlink neighbor notifications and presents to callers
* NeighborEvents describing each event. Callers can provide a consumer instance
* to both filter (e.g. by interface index and IP address) and handle the
* generated NeighborEvents.
*
* @hide
*/
public class IpNeighborMonitor extends PacketReader {
private static final String TAG = IpNeighborMonitor.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean VDBG = false;
/**
* Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
* for the given IP address on the specified interface index.
*
* @return 0 if the request was successfully passed to the kernel; otherwise return
* a non-zero error code.
*/
public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
if (DBG) { Log.d(TAG, msgSnippet); }
final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
try {
NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
} catch (ErrnoException e) {
Log.e(TAG, "Error " + msgSnippet + ": " + e);
return -e.errno;
}
return 0;
}
public static class NeighborEvent {
final long elapsedMs;
final short msgType;
final int ifindex;
final InetAddress ip;
final short nudState;
final byte[] linkLayerAddr;
public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
short nudState, byte[] linkLayerAddr) {
this.elapsedMs = elapsedMs;
this.msgType = msgType;
this.ifindex = ifindex;
this.ip = ip;
this.nudState = nudState;
this.linkLayerAddr = linkLayerAddr;
}
boolean isConnected() {
return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
StructNdMsg.isNudStateConnected(nudState);
}
boolean isValid() {
return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
StructNdMsg.isNudStateValid(nudState);
}
@Override
public String toString() {
final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
return j.add("@" + elapsedMs)
.add(NetlinkConstants.stringForNlMsgType(msgType))
.add("if=" + ifindex)
.add(ip.getHostAddress())
.add(StructNdMsg.stringForNudState(nudState))
.add("[" + NetlinkConstants.hexify(linkLayerAddr) + "]")
.toString();
}
}
public interface NeighborEventConsumer {
// Every neighbor event received on the netlink socket is passed in
// here. Subclasses should filter for events of interest.
public void accept(NeighborEvent event);
}
private final SharedLog mLog;
private final NeighborEventConsumer mConsumer;
public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
mLog = log.forSubComponent(TAG);
mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
}
@Override
protected FileDescriptor createFd() {
FileDescriptor fd = null;
try {
fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE);
Os.bind(fd, (SocketAddress)(new NetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)));
Os.connect(fd, (SocketAddress)(new NetlinkSocketAddress(0, 0)));
if (VDBG) {
final NetlinkSocketAddress nlAddr = (NetlinkSocketAddress) Os.getsockname(fd);
Log.d(TAG, "bound to sockaddr_nl{"
+ BitUtils.uint32(nlAddr.getPortId()) + ", "
+ nlAddr.getGroupsMask()
+ "}");
}
} catch (ErrnoException|SocketException e) {
logError("Failed to create rtnetlink socket", e);
IoUtils.closeQuietly(fd);
return null;
}
return fd;
}
@Override
protected void handlePacket(byte[] recvbuf, int length) {
final long whenMs = SystemClock.elapsedRealtime();
final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
byteBuffer.order(ByteOrder.nativeOrder());
parseNetlinkMessageBuffer(byteBuffer, whenMs);
}
private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
while (byteBuffer.remaining() > 0) {
final int position = byteBuffer.position();
final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
if (nlMsg == null || nlMsg.getHeader() == null) {
byteBuffer.position(position);
mLog.e("unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
break;
}
final int srcPortId = nlMsg.getHeader().nlmsg_pid;
if (srcPortId != 0) {
mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
break;
}
if (nlMsg instanceof NetlinkErrorMessage) {
mLog.e("netlink error: " + nlMsg);
continue;
} else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
mLog.i("non-rtnetlink neighbor msg: " + nlMsg);
continue;
}
evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
}
}
private void evaluateRtNetlinkNeighborMessage(
RtNetlinkNeighborMessage neighMsg, long whenMs) {
final short msgType = neighMsg.getHeader().nlmsg_type;
final StructNdMsg ndMsg = neighMsg.getNdHeader();
if (ndMsg == null) {
mLog.e("RtNetlinkNeighborMessage without ND message header!");
return;
}
final int ifindex = ndMsg.ndm_ifindex;
final InetAddress destination = neighMsg.getDestination();
final short nudState =
(msgType == NetlinkConstants.RTM_DELNEIGH)
? StructNdMsg.NUD_NONE
: ndMsg.ndm_state;
final NeighborEvent event = new NeighborEvent(
whenMs, msgType, ifindex, destination, nudState, neighMsg.getLinkLayerAddress());
if (VDBG) {
Log.d(TAG, neighMsg.toString());
}
if (DBG) {
Log.d(TAG, event.toString());
}
mConsumer.accept(event);
}
}

View File

@@ -22,30 +22,27 @@ import android.net.LinkProperties;
import android.net.LinkProperties.ProvisioningChange;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpReachabilityEvent;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkErrorMessage;
import android.net.netlink.NetlinkMessage;
import android.net.netlink.NetlinkSocket;
import android.net.netlink.RtNetlinkNeighborMessage;
import android.net.netlink.StructNdMsg;
import android.net.netlink.StructNdaCacheInfo;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.OsConstants;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils.Dump;
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -134,6 +131,8 @@ import java.util.Set;
* state it may be best for the link to disconnect completely and
* reconnect afresh.
*
* Accessing an instance of this class from multiple threads is NOT safe.
*
* @hide
*/
public class IpReachabilityMonitor {
@@ -169,64 +168,33 @@ public class IpReachabilityMonitor {
}
}
private final Object mLock = new Object();
private final String mInterfaceName;
private final int mInterfaceIndex;
private final IpNeighborMonitor mIpNeighborMonitor;
private final SharedLog mLog;
private final Callback mCallback;
private final Dependencies mDependencies;
private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
private final NetlinkSocketObserver mNetlinkSocketObserver;
private final Thread mObserverThread;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
@GuardedBy("mLock")
private LinkProperties mLinkProperties = new LinkProperties();
// TODO: consider a map to a private NeighborState class holding more
// information than a single NUD state entry.
@GuardedBy("mLock")
private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
@GuardedBy("mLock")
private int mIpWatchListVersion;
private volatile boolean mRunning;
private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
// Time in milliseconds of the last forced probe request.
private volatile long mLastProbeTimeMs;
/**
* Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
* for the given IP address on the specified interface index.
*
* @return 0 if the request was successfully passed to the kernel; otherwise return
* a non-zero error code.
*/
private static int probeNeighbor(int ifIndex, InetAddress ip) {
final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
if (DBG) { Log.d(TAG, msgSnippet); }
final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
try {
NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
} catch (ErrnoException e) {
Log.e(TAG, "Error " + msgSnippet + ": " + e);
return -e.errno;
}
return 0;
public IpReachabilityMonitor(
Context context, String ifName, Handler h, SharedLog log, Callback callback) {
this(context, ifName, h, log, callback, null);
}
public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) {
this(context, ifName, log, callback, null);
}
public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback,
public IpReachabilityMonitor(
Context context, String ifName, Handler h, SharedLog log, Callback callback,
MultinetworkPolicyTracker tracker) {
this(ifName, getInterfaceIndex(ifName), log, callback, tracker,
this(ifName, getInterfaceIndex(ifName), h, log, callback, tracker,
Dependencies.makeDefault(context, ifName));
}
@VisibleForTesting
IpReachabilityMonitor(String ifName, int ifIndex, SharedLog log, Callback callback,
IpReachabilityMonitor(String ifName, int ifIndex, Handler h, SharedLog log, Callback callback,
MultinetworkPolicyTracker tracker, Dependencies dependencies) {
mInterfaceName = ifName;
mLog = log.forSubComponent(TAG);
@@ -234,47 +202,56 @@ public class IpReachabilityMonitor {
mMultinetworkPolicyTracker = tracker;
mInterfaceIndex = ifIndex;
mDependencies = dependencies;
mNetlinkSocketObserver = new NetlinkSocketObserver();
mObserverThread = new Thread(mNetlinkSocketObserver);
mObserverThread.start();
mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
(NeighborEvent event) -> {
if (mInterfaceIndex != event.ifindex) return;
if (!mNeighborWatchList.containsKey(event.ip)) return;
final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
// TODO: Consider what to do with other states that are not within
// NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
if (event.nudState == StructNdMsg.NUD_FAILED) {
mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
handleNeighborLost(event);
}
});
mIpNeighborMonitor.start();
}
public void stop() {
mRunning = false;
mIpNeighborMonitor.stop();
clearLinkProperties();
mNetlinkSocketObserver.clearNetlinkSocket();
}
// TODO: add a public dump() method that can be called during a bug report.
public void dump(PrintWriter pw) {
DumpUtils.dumpAsync(
mIpNeighborMonitor.getHandler(),
new Dump() {
@Override
public void dump(PrintWriter pw, String prefix) {
pw.println(describeWatchList("\n"));
}
},
pw, "", 1000);
}
private String describeWatchList() {
final String delimiter = ", ";
StringBuilder sb = new StringBuilder();
synchronized (mLock) {
sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, ");
sb.append("v{" + mIpWatchListVersion + "}, ");
sb.append("ntable=[");
boolean firstTime = true;
for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
if (firstTime) {
firstTime = false;
} else {
sb.append(delimiter);
}
sb.append(entry.getKey().getHostAddress() + "/" +
StructNdMsg.stringForNudState(entry.getValue()));
}
sb.append("]");
private String describeWatchList() { return describeWatchList(" "); }
private String describeWatchList(String sep) {
final StringBuilder sb = new StringBuilder();
sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}," + sep);
sb.append("ntable=[" + sep);
String delimiter = "";
for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
delimiter = "," + sep;
}
sb.append("]");
return sb.toString();
}
private boolean isWatching(InetAddress ip) {
synchronized (mLock) {
return mRunning && mIpWatchList.containsKey(ip);
}
}
private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
for (RouteInfo route : routes) {
if (!route.hasGateway() && route.matches(ip)) {
@@ -284,13 +261,6 @@ public class IpReachabilityMonitor {
return false;
}
private short getNeighborStateLocked(InetAddress ip) {
if (mIpWatchList.containsKey(ip)) {
return mIpWatchList.get(ip);
}
return StructNdMsg.NUD_NONE;
}
public void updateLinkProperties(LinkProperties lp) {
if (!mInterfaceName.equals(lp.getInterfaceName())) {
// TODO: figure out whether / how to cope with interface changes.
@@ -299,70 +269,63 @@ public class IpReachabilityMonitor {
return;
}
synchronized (mLock) {
mLinkProperties = new LinkProperties(lp);
Map<InetAddress, Short> newIpWatchList = new HashMap<>();
mLinkProperties = new LinkProperties(lp);
Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
final List<RouteInfo> routes = mLinkProperties.getRoutes();
for (RouteInfo route : routes) {
if (route.hasGateway()) {
InetAddress gw = route.getGateway();
if (isOnLink(routes, gw)) {
newIpWatchList.put(gw, getNeighborStateLocked(gw));
}
final List<RouteInfo> routes = mLinkProperties.getRoutes();
for (RouteInfo route : routes) {
if (route.hasGateway()) {
InetAddress gw = route.getGateway();
if (isOnLink(routes, gw)) {
newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
}
}
for (InetAddress nameserver : lp.getDnsServers()) {
if (isOnLink(routes, nameserver)) {
newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));
}
}
mIpWatchList = newIpWatchList;
mIpWatchListVersion++;
}
for (InetAddress dns : lp.getDnsServers()) {
if (isOnLink(routes, dns)) {
newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
}
}
mNeighborWatchList = newNeighborWatchList;
if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
}
public void clearLinkProperties() {
synchronized (mLock) {
mLinkProperties.clear();
mIpWatchList.clear();
mIpWatchListVersion++;
}
mLinkProperties.clear();
mNeighborWatchList.clear();
if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
}
private void handleNeighborLost(String msg) {
private void handleNeighborLost(NeighborEvent event) {
final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
InetAddress ip = null;
final ProvisioningChange delta;
synchronized (mLock) {
LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
// TODO: Consider using NeighborEvent#isValid() here; it's more
// strict but may interact badly if other entries are somehow in
// NUD_INCOMPLETE (say, during network attach).
if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
if (entry.getValue() != StructNdMsg.NUD_FAILED) {
continue;
}
ip = entry.getKey();
for (RouteInfo route : mLinkProperties.getRoutes()) {
if (ip.equals(route.getGateway())) {
whatIfLp.removeRoute(route);
}
}
if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
// We should do this unconditionally, but alas we cannot: b/31827713.
whatIfLp.removeDnsServer(ip);
ip = entry.getKey();
for (RouteInfo route : mLinkProperties.getRoutes()) {
if (ip.equals(route.getGateway())) {
whatIfLp.removeRoute(route);
}
}
delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
// We should do this unconditionally, but alas we cannot: b/31827713.
whatIfLp.removeDnsServer(ip);
}
}
final ProvisioningChange delta = LinkProperties.compareProvisioning(
mLinkProperties, whatIfLp);
if (delta == ProvisioningChange.LOST_PROVISIONING) {
final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
Log.w(TAG, logMsg);
if (mCallback != null) {
// TODO: remove |ip| when the callback signature no longer has
@@ -378,12 +341,9 @@ public class IpReachabilityMonitor {
}
public void probeAll() {
final List<InetAddress> ipProbeList;
synchronized (mLock) {
ipProbeList = new ArrayList<>(mIpWatchList.keySet());
}
final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
if (!ipProbeList.isEmpty() && mRunning) {
if (!ipProbeList.isEmpty()) {
// Keep the CPU awake long enough to allow all ARP/ND
// probes a reasonable chance at success. See b/23197666.
//
@@ -394,13 +354,10 @@ public class IpReachabilityMonitor {
}
for (InetAddress target : ipProbeList) {
if (!mRunning) {
break;
}
final int returnValue = probeNeighbor(mInterfaceIndex, target);
final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceIndex, target);
mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
target.getHostAddress(), returnValue));
logEvent(IpReachabilityEvent.PROBE, returnValue);
target.getHostAddress(), rval));
logEvent(IpReachabilityEvent.PROBE, rval);
}
mLastProbeTimeMs = SystemClock.elapsedRealtime();
}
@@ -446,153 +403,4 @@ public class IpReachabilityMonitor {
int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
}
// TODO: simplify the number of objects by making this extend Thread.
private final class NetlinkSocketObserver implements Runnable {
private NetlinkSocket mSocket;
@Override
public void run() {
if (VDBG) { Log.d(TAG, "Starting observing thread."); }
mRunning = true;
try {
setupNetlinkSocket();
} catch (ErrnoException | SocketException e) {
Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
mRunning = false;
}
while (mRunning) {
final ByteBuffer byteBuffer;
try {
byteBuffer = recvKernelReply();
} catch (ErrnoException e) {
if (mRunning) { Log.w(TAG, "ErrnoException: ", e); }
break;
}
final long whenMs = SystemClock.elapsedRealtime();
if (byteBuffer == null) {
continue;
}
parseNetlinkMessageBuffer(byteBuffer, whenMs);
}
clearNetlinkSocket();
mRunning = false; // Not a no-op when ErrnoException happened.
if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
}
private void clearNetlinkSocket() {
if (mSocket != null) {
mSocket.close();
}
}
// TODO: Refactor the main loop to recreate the socket upon recoverable errors.
private void setupNetlinkSocket() throws ErrnoException, SocketException {
clearNetlinkSocket();
mSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
final NetlinkSocketAddress listenAddr = new NetlinkSocketAddress(
0, OsConstants.RTMGRP_NEIGH);
mSocket.bind(listenAddr);
if (VDBG) {
final NetlinkSocketAddress nlAddr = mSocket.getLocalAddress();
Log.d(TAG, "bound to sockaddr_nl{"
+ ((long) (nlAddr.getPortId() & 0xffffffff)) + ", "
+ nlAddr.getGroupsMask()
+ "}");
}
}
private ByteBuffer recvKernelReply() throws ErrnoException {
try {
return mSocket.recvMessage(0);
} catch (InterruptedIOException e) {
// Interruption or other error, e.g. another thread closed our file descriptor.
} catch (ErrnoException e) {
if (e.errno != OsConstants.EAGAIN) {
throw e;
}
}
return null;
}
private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
while (byteBuffer.remaining() > 0) {
final int position = byteBuffer.position();
final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
if (nlMsg == null || nlMsg.getHeader() == null) {
byteBuffer.position(position);
Log.e(TAG, "unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
break;
}
final int srcPortId = nlMsg.getHeader().nlmsg_pid;
if (srcPortId != 0) {
Log.e(TAG, "non-kernel source portId: " + ((long) (srcPortId & 0xffffffff)));
break;
}
if (nlMsg instanceof NetlinkErrorMessage) {
Log.e(TAG, "netlink error: " + nlMsg);
continue;
} else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
if (DBG) {
Log.d(TAG, "non-rtnetlink neighbor msg: " + nlMsg);
}
continue;
}
evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
}
}
private void evaluateRtNetlinkNeighborMessage(
RtNetlinkNeighborMessage neighMsg, long whenMs) {
final StructNdMsg ndMsg = neighMsg.getNdHeader();
if (ndMsg == null || ndMsg.ndm_ifindex != mInterfaceIndex) {
return;
}
final InetAddress destination = neighMsg.getDestination();
if (!isWatching(destination)) {
return;
}
final short msgType = neighMsg.getHeader().nlmsg_type;
final short nudState = ndMsg.ndm_state;
final String eventMsg = "NeighborEvent{"
+ "elapsedMs=" + whenMs + ", "
+ destination.getHostAddress() + ", "
+ "[" + NetlinkConstants.hexify(neighMsg.getLinkLayerAddress()) + "], "
+ NetlinkConstants.stringForNlMsgType(msgType) + ", "
+ StructNdMsg.stringForNudState(nudState)
+ "}";
if (VDBG) {
Log.d(TAG, neighMsg.toString());
} else if (DBG) {
Log.d(TAG, eventMsg);
}
synchronized (mLock) {
if (mIpWatchList.containsKey(destination)) {
final short value =
(msgType == NetlinkConstants.RTM_DELNEIGH)
? StructNdMsg.NUD_NONE
: nudState;
mIpWatchList.put(destination, value);
}
}
if (nudState == StructNdMsg.NUD_FAILED) {
Log.w(TAG, "ALERT: " + eventMsg);
handleNeighborLost(eventMsg);
}
}
}
}

View File

@@ -16,16 +16,24 @@
package android.net.netlink;
import static android.system.OsConstants.AF_NETLINK;
import static android.system.OsConstants.EIO;
import static android.system.OsConstants.EPROTO;
import static android.system.OsConstants.ETIMEDOUT;
import static android.system.OsConstants.SO_RCVBUF;
import static android.system.OsConstants.SO_RCVTIMEO;
import static android.system.OsConstants.SO_SNDTIMEO;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOL_SOCKET;
import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructTimeval;
import android.util.Log;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.SocketAddress;
@@ -37,28 +45,27 @@ import java.nio.ByteOrder;
/**
* NetlinkSocket
*
* A small wrapper class to assist with AF_NETLINK socket operations.
* A small static class to assist with AF_NETLINK socket operations.
*
* @hide
*/
public class NetlinkSocket implements Closeable {
public class NetlinkSocket {
private static final String TAG = "NetlinkSocket";
private static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
private static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
final private FileDescriptor mDescriptor;
private NetlinkSocketAddress mAddr;
private long mLastRecvTimeoutMs;
private long mLastSendTimeoutMs;
public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
final long IO_TIMEOUT = 300L;
try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) {
final long IO_TIMEOUT = 300L;
nlSocket.connectToKernel();
nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
FileDescriptor fd;
try {
fd = forProto(nlProto);
connectToKernel(fd);
sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
// recvMessage() guaranteed to not return null if it did not throw.
final NetlinkMessage response = NetlinkMessage.parse(bytes);
if (response != null && response instanceof NetlinkErrorMessage &&
@@ -81,61 +88,30 @@ public class NetlinkSocket implements Closeable {
errmsg = response.toString();
}
Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
throw new ErrnoException(errmsg, OsConstants.EPROTO);
throw new ErrnoException(errmsg, EPROTO);
}
} catch (InterruptedIOException e) {
Log.e(TAG, errPrefix, e);
throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e);
throw new ErrnoException(errPrefix, ETIMEDOUT, e);
} catch (SocketException e) {
Log.e(TAG, errPrefix, e);
throw new ErrnoException(errPrefix, OsConstants.EIO, e);
throw new ErrnoException(errPrefix, EIO, e);
}
IoUtils.closeQuietly(fd);
}
public NetlinkSocket(int nlProto) throws ErrnoException {
mDescriptor = Os.socket(
OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
Os.setsockoptInt(
mDescriptor, OsConstants.SOL_SOCKET,
OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
public static FileDescriptor forProto(int nlProto) throws ErrnoException {
final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
return fd;
}
public NetlinkSocketAddress getLocalAddress() throws ErrnoException {
return (NetlinkSocketAddress) Os.getsockname(mDescriptor);
public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0)));
}
public void bind(NetlinkSocketAddress localAddr) throws ErrnoException, SocketException {
Os.bind(mDescriptor, (SocketAddress)localAddr);
}
public void connectTo(NetlinkSocketAddress peerAddr)
throws ErrnoException, SocketException {
Os.connect(mDescriptor, (SocketAddress) peerAddr);
}
public void connectToKernel() throws ErrnoException, SocketException {
connectTo(new NetlinkSocketAddress(0, 0));
}
/**
* Wait indefinitely (or until underlying socket error) for a
* netlink message of at most DEFAULT_RECV_BUFSIZE size.
*/
public ByteBuffer recvMessage()
throws ErrnoException, InterruptedIOException {
return recvMessage(DEFAULT_RECV_BUFSIZE, 0);
}
/**
* Wait up to |timeoutMs| (or until underlying socket error) for a
* netlink message of at most DEFAULT_RECV_BUFSIZE size.
*/
public ByteBuffer recvMessage(long timeoutMs) throws ErrnoException, InterruptedIOException {
return recvMessage(DEFAULT_RECV_BUFSIZE, timeoutMs);
}
private void checkTimeout(long timeoutMs) {
private static void checkTimeout(long timeoutMs) {
if (timeoutMs < 0) {
throw new IllegalArgumentException("Negative timeouts not permitted");
}
@@ -147,21 +123,14 @@ public class NetlinkSocket implements Closeable {
*
* Multi-threaded calls with different timeouts will cause unexpected results.
*/
public ByteBuffer recvMessage(int bufsize, long timeoutMs)
public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
throws ErrnoException, IllegalArgumentException, InterruptedIOException {
checkTimeout(timeoutMs);
synchronized (mDescriptor) {
if (mLastRecvTimeoutMs != timeoutMs) {
Os.setsockoptTimeval(mDescriptor,
OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
StructTimeval.fromMillis(timeoutMs));
mLastRecvTimeoutMs = timeoutMs;
}
}
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
int length = Os.read(mDescriptor, byteBuffer);
int length = Os.read(fd, byteBuffer);
if (length == bufsize) {
Log.w(TAG, "maximum read");
}
@@ -171,40 +140,17 @@ public class NetlinkSocket implements Closeable {
return byteBuffer;
}
/**
* Send a message to a peer to which this socket has previously connected.
*
* This blocks until completion or an error occurs.
*/
public boolean sendMessage(byte[] bytes, int offset, int count)
throws ErrnoException, InterruptedIOException {
return sendMessage(bytes, offset, count, 0);
}
/**
* Send a message to a peer to which this socket has previously connected,
* waiting at most |timeoutMs| milliseconds for the send to complete.
*
* Multi-threaded calls with different timeouts will cause unexpected results.
*/
public boolean sendMessage(byte[] bytes, int offset, int count, long timeoutMs)
public static int sendMessage(
FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
throws ErrnoException, IllegalArgumentException, InterruptedIOException {
checkTimeout(timeoutMs);
synchronized (mDescriptor) {
if (mLastSendTimeoutMs != timeoutMs) {
Os.setsockoptTimeval(mDescriptor,
OsConstants.SOL_SOCKET, OsConstants.SO_SNDTIMEO,
StructTimeval.fromMillis(timeoutMs));
mLastSendTimeoutMs = timeoutMs;
}
}
return (count == Os.write(mDescriptor, bytes, offset, count));
}
@Override
public void close() {
IoUtils.closeQuietly(mDescriptor);
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
return Os.write(fd, bytes, offset, count);
}
}

View File

@@ -63,6 +63,11 @@ public class StructNdMsg {
return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0);
}
public static boolean isNudStateValid(short nudState) {
return (isNudStateConnected(nudState) ||
((nudState & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
}
// Neighbor Cache Entry Flags
public static byte NTF_USE = (byte) 0x01;
public static byte NTF_SELF = (byte) 0x02;
@@ -143,7 +148,7 @@ public class StructNdMsg {
}
public boolean nudValid() {
return (nudConnected() || ((ndm_state & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
return isNudStateValid(ndm_state);
}
@Override

View File

@@ -67,7 +67,7 @@ import java.io.IOException;
*
* @hide
*/
public abstract class BlockingSocketReader {
public abstract class PacketReader {
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
private static final int UNREGISTER_THIS_FD = 0;
@@ -83,11 +83,11 @@ public abstract class BlockingSocketReader {
IoUtils.closeQuietly(fd);
}
protected BlockingSocketReader(Handler h) {
protected PacketReader(Handler h) {
this(h, DEFAULT_RECV_BUF_SIZE);
}
protected BlockingSocketReader(Handler h, int recvbufsize) {
protected PacketReader(Handler h, int recvbufsize) {
mHandler = h;
mQueue = mHandler.getLooper().getQueue();
mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
@@ -115,6 +115,8 @@ public abstract class BlockingSocketReader {
}
}
public Handler getHandler() { return mHandler; }
public final int recvBufSize() { return mPacket.length; }
public final long numPacketsReceived() { return mPacketsReceived; }

View File

@@ -18,10 +18,12 @@ package android.net.ip;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.when;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Looper;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -42,14 +44,18 @@ public class IpReachabilityMonitorTest {
@Mock IpReachabilityMonitor.Callback mCallback;
@Mock IpReachabilityMonitor.Dependencies mDependencies;
@Mock SharedLog mLog;
Handler mHandler;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
mHandler = new Handler(Looper.getMainLooper());
}
IpReachabilityMonitor makeMonitor() {
return new IpReachabilityMonitor("fake0", 1, mLog, mCallback, null, mDependencies);
return new IpReachabilityMonitor(
"fake0", 1, mHandler, mLog, mCallback, null, mDependencies);
}
@Test

View File

@@ -16,6 +16,8 @@
package android.net.netlink;
import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
import static android.system.OsConstants.NETLINK_ROUTE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -28,10 +30,12 @@ import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.OsConstants;
import android.system.Os;
import android.util.Log;
import libcore.io.IoUtils;
import java.io.InterruptedIOException;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -46,29 +50,28 @@ public class NetlinkSocketTest {
@Test
public void testBasicWorkingGetNeighborsQuery() throws Exception {
NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
assertNotNull(s);
final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
assertNotNull(fd);
s.connectToKernel();
NetlinkSocket.connectToKernel(fd);
NetlinkSocketAddress localAddr = s.getLocalAddress();
final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
assertNotNull(localAddr);
assertEquals(0, localAddr.getGroupsMask());
assertTrue(0 != localAddr.getPortId());
final int TEST_SEQNO = 5;
final byte[] request = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
assertNotNull(request);
final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
assertNotNull(req);
final long TIMEOUT = 500;
assertTrue(s.sendMessage(request, 0, request.length, TIMEOUT));
assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
int neighMessageCount = 0;
int doneMessageCount = 0;
while (doneMessageCount == 0) {
ByteBuffer response = null;
response = s.recvMessage(TIMEOUT);
ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
assertNotNull(response);
assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
assertEquals(0, response.position());
@@ -100,30 +103,6 @@ public class NetlinkSocketTest {
// TODO: make sure this test passes sanely in airplane mode.
assertTrue(neighMessageCount > 0);
s.close();
}
@Test
public void testRepeatedCloseCallsAreQuiet() throws Exception {
// Create a working NetlinkSocket.
NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
assertNotNull(s);
s.connectToKernel();
NetlinkSocketAddress localAddr = s.getLocalAddress();
assertNotNull(localAddr);
assertEquals(0, localAddr.getGroupsMask());
assertTrue(0 != localAddr.getPortId());
// Close once.
s.close();
// Test that it is closed.
boolean expectedErrorSeen = false;
try {
localAddr = s.getLocalAddress();
} catch (ErrnoException e) {
expectedErrorSeen = true;
}
assertTrue(expectedErrorSeen);
// Close once more.
s.close();
IoUtils.closeQuietly(fd);
}
}

View File

@@ -16,7 +16,7 @@
package android.net.util;
import static android.net.util.BlockingSocketReader.DEFAULT_RECV_BUF_SIZE;
import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
import static android.system.OsConstants.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -53,13 +53,13 @@ import org.junit.Test;
import libcore.io.IoBridge;
/**
* Tests for BlockingSocketReader.
* Tests for PacketReader.
*
* @hide
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class BlockingSocketReaderTest {
public class PacketReaderTest {
static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
@@ -69,9 +69,9 @@ public class BlockingSocketReaderTest {
protected byte[] mLastRecvBuf;
protected boolean mStopped;
protected HandlerThread mHandlerThread;
protected BlockingSocketReader mReceiver;
protected PacketReader mReceiver;
class UdpLoopbackReader extends BlockingSocketReader {
class UdpLoopbackReader extends PacketReader {
public UdpLoopbackReader(Handler h) {
super(h);
}
@@ -121,7 +121,7 @@ public class BlockingSocketReaderTest {
mLastRecvBuf = null;
mStopped = false;
mHandlerThread = new HandlerThread(BlockingSocketReaderTest.class.getSimpleName());
mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
mHandlerThread.start();
}
@@ -188,8 +188,8 @@ public class BlockingSocketReaderTest {
mReceiver = null;
}
class NullBlockingSocketReader extends BlockingSocketReader {
public NullBlockingSocketReader(Handler h, int recvbufsize) {
class NullPacketReader extends PacketReader {
public NullPacketReader(Handler h, int recvbufsize) {
super(h, recvbufsize);
}
@@ -202,7 +202,7 @@ public class BlockingSocketReaderTest {
final Handler h = mHandlerThread.getThreadHandler();
for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) {
final BlockingSocketReader b = new NullBlockingSocketReader(h, i);
final PacketReader b = new NullPacketReader(h, i);
assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
}
}