APF: add debug code.
1. Store additional debug information when RA filtering.
2. Add a toString method for RA packets.
3. Make "adb shell dumpsys connectivity apf" dump APF filter
state.
Bug: 26238573
Change-Id: I1441ff7bc90e63624f8b10a220b2ac97f4d390a5
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3c13577..b7fca1a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1784,6 +1784,38 @@
return false;
}
+ private void dumpNetworkDiagnostics(IndentingPrintWriter pw) {
+ final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
+ final long DIAG_TIME_MS = 5000;
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ // Start gathering diagnostic information.
+ netDiags.add(new NetworkDiagnostics(
+ nai.network,
+ new LinkProperties(nai.linkProperties), // Must be a copy.
+ DIAG_TIME_MS));
+ }
+
+ for (NetworkDiagnostics netDiag : netDiags) {
+ pw.println();
+ netDiag.waitForMeasurements();
+ netDiag.dump(pw);
+ }
+ }
+
+ private void dumpApf(IndentingPrintWriter pw) {
+ pw.println("APF filters:");
+ pw.increaseIndent();
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ if (nai.apfFilter != null) {
+ pw.println(nai.name() + ":");
+ pw.increaseIndent();
+ nai.apfFilter.dump(pw);
+ pw.decreaseIndent();
+ }
+ }
+ pw.decreaseIndent();
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1796,23 +1828,13 @@
return;
}
- final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
if (argsContain(args, "--diag")) {
- final long DIAG_TIME_MS = 5000;
- for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- // Start gathering diagnostic information.
- netDiags.add(new NetworkDiagnostics(
- nai.network,
- new LinkProperties(nai.linkProperties), // Must be a copy.
- DIAG_TIME_MS));
- }
+ dumpNetworkDiagnostics(pw);
+ return;
+ }
- for (NetworkDiagnostics netDiag : netDiags) {
- pw.println();
- netDiag.waitForMeasurements();
- netDiag.dump(pw);
- }
-
+ if (argsContain(args, "apf")) {
+ dumpApf(pw);
return;
}
@@ -1878,6 +1900,9 @@
pw.println();
mKeepaliveTracker.dump(pw);
+ pw.println();
+ dumpApf(pw);
+
if (mInetLog != null && mInetLog.size() > 0) {
pw.println();
pw.println("Inet condition reports:");
diff --git a/services/core/java/com/android/server/connectivity/ApfFilter.java b/services/core/java/com/android/server/connectivity/ApfFilter.java
index 4696bd8..8195319 100644
--- a/services/core/java/com/android/server/connectivity/ApfFilter.java
+++ b/services/core/java/com/android/server/connectivity/ApfFilter.java
@@ -29,14 +29,19 @@
import android.util.Pair;
import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.ConnectivityService;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.Thread;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
+import java.nio.BufferUnderflowException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -85,6 +90,7 @@
}
private static final String TAG = "ApfFilter";
+ private static final boolean VDBG = true;
private final ConnectivityService mConnectivityService;
private final NetworkAgentInfo mNai;
@@ -152,6 +158,8 @@
private static final int ETH_HEADER_LEN = 14;
private static final int IPV6_HEADER_LEN = 40;
+ private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
+ private static final int IPV6_DST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
// From RFC4861:
private static final int ICMP6_RA_HEADER_LEN = 16;
@@ -192,6 +200,66 @@
// When the packet was last captured, in seconds since Unix Epoch
long mLastSeen;
+ // For debugging only. Offsets into the packet where PIOs are.
+ private final ArrayList<Integer> mPrefixOptionOffsets;
+ // For debugging only. How many times this RA was seen.
+ int seenCount = 0;
+
+
+ private String IPv6AddresstoString(int pos) {
+ try {
+ byte[] array = mPacket.array();
+ // Can't just call copyOfRange() and see if it throws, because if it reads past the
+ // end it pads with zeros instead of throwing.
+ if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) {
+ return "???";
+ }
+ byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16);
+ InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes);
+ return address.getHostAddress();
+ } catch (UnsupportedOperationException e) {
+ // array() failed. Cannot happen, mPacket is array-backed and read-write.
+ return "???";
+ } catch (ClassCastException | UnknownHostException e) {
+ // Cannot happen.
+ return "???";
+ }
+ }
+
+ // Can't be static because it's in a non-static inner class.
+ // TODO: Make this final once RA is its own class.
+ private int uint8(byte b) {
+ return b & 0xff;
+ }
+
+ private int uint16(short s) {
+ return s & 0xffff;
+ }
+
+ private long uint32(int s) {
+ return s & 0xffffffff;
+ }
+
+ public String toString() {
+ try {
+ StringBuffer sb = new StringBuffer();
+ sb.append(String.format("RA %s -> %s %d ",
+ IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
+ IPv6AddresstoString(IPV6_DST_ADDR_OFFSET),
+ uint16(mPacket.getShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET))));
+ for (int i: mPrefixOptionOffsets) {
+ String prefix = IPv6AddresstoString(i + 16);
+ int length = uint8(mPacket.get(i + 2));
+ long valid = mPacket.getInt(i + 4);
+ long preferred = mPacket.getInt(i + 8);
+ sb.append(String.format("%s/%d %d/%d ", prefix, length, valid, preferred));
+ }
+ return sb.toString();
+ } catch (BufferUnderflowException | IndexOutOfBoundsException e) {
+ return "<Malformed RA>";
+ }
+ }
+
/**
* Add a binary range of the packet that does not include a lifetime to mNonLifetimes.
* Assumes mPacket.position() is as far as we've parsed the packet.
@@ -230,6 +298,7 @@
ICMP6_RA_ROUTER_LIFETIME_LEN);
// Parse ICMP6 options
+ mPrefixOptionOffsets = new ArrayList<>();
mPacket.position(ICMP6_RA_OPTION_OFFSET);
while (mPacket.hasRemaining()) {
int optionType = ((int)mPacket.get(mPacket.position())) & 0xff;
@@ -244,6 +313,7 @@
lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN);
+ mPrefixOptionOffsets.add(mPacket.position());
break;
// These three options have the same lifetime offset and size, so process
// together:
@@ -383,6 +453,9 @@
// How long should the last installed filter program live for? In seconds.
private long mLastInstalledProgramMinLifetime;
+ // For debugging only. The length in bytes of the last program.
+ private long mLastInstalledProgramLength;
+
private void installNewProgram() {
if (mRas.size() == 0) return;
final byte[] program;
@@ -422,7 +495,12 @@
}
mLastTimeInstalledProgram = curTime();
mLastInstalledProgramMinLifetime = programMinLifetime;
- hexDump("Installing filter: ", program, program.length);
+ mLastInstalledProgramLength = program.length;
+ if (VDBG) {
+ hexDump("Installing filter: ", program, program.length);
+ } else {
+ Log.d(TAG, "Installing filter length=" + program.length);
+ }
mConnectivityService.pushApfProgramToNetwork(mNai, program);
}
@@ -441,16 +519,17 @@
}
private void processRa(byte[] packet, int length) {
- hexDump("Read packet = ", packet, length);
+ if (VDBG) hexDump("Read packet = ", packet, length);
// Have we seen this RA before?
for (int i = 0; i < mRas.size(); i++) {
Ra ra = mRas.get(i);
if (ra.matches(packet, length)) {
- log("matched RA");
+ if (VDBG) log("matched RA " + ra);
// Update lifetimes.
ra.mLastSeen = curTime();
ra.mMinLifetime = ra.minLifetime(packet, length);
+ ra.seenCount++;
// Keep mRas in LRU order so as to prioritize generating filters for recently seen
// RAs. LRU prioritizes this because RA filters are generated in order from mRas
@@ -468,7 +547,7 @@
// Purge expired RAs.
for (int i = 0; i < mRas.size();) {
if (mRas.get(i).isExpired()) {
- log("expired RA");
+ log("Expired RA " + mRas.get(i));
mRas.remove(i);
} else {
i++;
@@ -477,8 +556,9 @@
// TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
if (mRas.size() >= MAX_RAS) return;
try {
- log("adding RA");
- mRas.add(new Ra(packet, length));
+ Ra ra = new Ra(packet, length);
+ log("Adding " + ra);
+ mRas.add(ra);
} catch (Exception e) {
Log.e(TAG, "Error parsing RA: " + e);
return;
@@ -493,8 +573,8 @@
public static void maybeInstall(ConnectivityService connectivityService, NetworkAgentInfo nai) {
if (nai.networkMisc == null) return;
if (nai.networkMisc.apfVersionSupported == 0) return;
- if (nai.networkMisc.maximumApfProgramSize < 200) {
- Log.e(TAG, "Uselessly small APF size limit: " + nai.networkMisc.maximumApfProgramSize);
+ if (nai.networkMisc.maximumApfProgramSize < 512) {
+ Log.e(TAG, "Unacceptably small APF limit: " + nai.networkMisc.maximumApfProgramSize);
return;
}
// For now only support generating programs for Ethernet frames. If this restriction is
@@ -511,9 +591,35 @@
public void shutdown() {
if (mReceiveThread != null) {
- log("shuting down");
+ log("shutting down");
mReceiveThread.halt(); // Also closes socket.
mReceiveThread = null;
}
}
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("APF version: " + mNai.networkMisc.apfVersionSupported);
+ pw.println("Max program size: " + mNai.networkMisc.maximumApfProgramSize);
+ pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
+ if (mLastTimeInstalledProgram == 0) {
+ pw.println("No program installed.");
+ return;
+ }
+
+ pw.println(String.format(
+ "Last program length %d, installed %ds ago, lifetime %d",
+ mLastInstalledProgramLength, curTime() - mLastTimeInstalledProgram,
+ mLastInstalledProgramMinLifetime));
+
+ pw.println("RA filters:");
+ pw.increaseIndent();
+ for (Ra ra: mRas) {
+ pw.println(ra);
+ pw.increaseIndent();
+ pw.println(String.format(
+ "Seen: %d, last %ds ago", ra.seenCount, curTime() - ra.mLastSeen));
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
}