From 4fc3ee5be223122792ebc0ee8a05c93d93e26a52 Mon Sep 17 00:00:00 2001 From: Hugo Benichi Date: Thu, 2 Jun 2016 11:20:27 +0900 Subject: Log events at APF program generation Example: ConnectivityMetricsEvent(15:24:52.018, 0, 0): ApfProgramEvent(0/0 RAs 121B forever FLAG_MULTICAST_FILTER_ON) ConnectivityMetricsEvent(15:24:53.036, 0, 0): ApfProgramEvent(1/1 RAs 334B 600s) ConnectivityMetricsEvent(15:24:53.590, 0, 0): ApfProgramEvent(1/1 RAs 360B 600s FLAG_MULTICAST_FILTER_ON, FLAG_HAS_IPV4_ADDRESS) ConnectivityMetricsEvent(15:24:58.157, 0, 0): ApfProgramEvent(1/1 RAs 294B 599s FLAG_HAS_IPV4_ADDRESS) Bug: 28204408 Change-Id: I9c4c82861cf42eb2c7e7bf5471f05e8ff2fc560c --- api/system-current.txt | 13 ++ core/java/android/net/metrics/ApfProgramEvent.java | 137 +++++++++++++++++++++ core/java/android/net/metrics/IpManagerEvent.java | 1 + .../android/net/metrics/ValidationProbeEvent.java | 1 + services/net/java/android/net/apf/ApfFilter.java | 18 ++- .../servicestests/src/android/net/apf/ApfTest.java | 6 +- 6 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 core/java/android/net/metrics/ApfProgramEvent.java diff --git a/api/system-current.txt b/api/system-current.txt index 3fa518a43e49..77414fb45cb9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -26018,6 +26018,19 @@ package android.net.http { package android.net.metrics { + public final class ApfProgramEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int FLAG_HAS_IPV4_ADDRESS = 1; // 0x1 + field public static final int FLAG_MULTICAST_FILTER_ON = 0; // 0x0 + field public final int currentRas; + field public final int filteredRas; + field public final int flags; + field public final long lifetime; + field public final int programLength; + } + public final class DefaultNetworkEvent implements android.os.Parcelable { method public int describeContents(); method public static void logEvent(int, int[], int, boolean, boolean); diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java new file mode 100644 index 000000000000..3cd058cb6dc5 --- /dev/null +++ b/core/java/android/net/metrics/ApfProgramEvent.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016 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.metrics; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import com.android.internal.util.MessageUtils; + +/** + * An event logged when there is a change or event that requires updating the + * the APF program in place with a new APF program. + * {@hide} + */ +@SystemApi +public final class ApfProgramEvent implements Parcelable { + + // Bitflag constants describing what an Apf program filters. + // Bits are indexeds from LSB to MSB, starting at index 0. + // TODO: use @IntDef + public static final int FLAG_MULTICAST_FILTER_ON = 0; + public static final int FLAG_HAS_IPV4_ADDRESS = 1; + + public final long lifetime; // Lifetime of the program in seconds + public final int filteredRas; // Number of RAs filtered by the APF program + public final int currentRas; // Total number of current RAs at generation time + public final int programLength; // Length of the APF program in bytes + public final int flags; // Bitfield compound of FLAG_* constants + + /** {@hide} */ + public ApfProgramEvent( + long lifetime, int filteredRas, int currentRas, int programLength, int flags) { + this.lifetime = lifetime; + this.filteredRas = filteredRas; + this.currentRas = currentRas; + this.programLength = programLength; + this.flags = flags; + } + + private ApfProgramEvent(Parcel in) { + this.lifetime = in.readLong(); + this.filteredRas = in.readInt(); + this.currentRas = in.readInt(); + this.programLength = in.readInt(); + this.flags = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(lifetime); + out.writeInt(filteredRas); + out.writeInt(currentRas); + out.writeInt(programLength); + out.writeInt(flags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever"; + return String.format("ApfProgramEvent(%d/%d RAs %dB %s %s)", + filteredRas, currentRas, programLength, lifetimeString, namesOf(flags)); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public ApfProgramEvent createFromParcel(Parcel in) { + return new ApfProgramEvent(in); + } + + public ApfProgramEvent[] newArray(int size) { + return new ApfProgramEvent[size]; + } + }; + + /** {@hide} */ + public static int flagsFor(boolean hasIPv4, boolean multicastFilterOn) { + int bitfield = 0; + if (hasIPv4) { + bitfield |= (1 << FLAG_HAS_IPV4_ADDRESS); + } + if (multicastFilterOn) { + bitfield |= (1 << FLAG_MULTICAST_FILTER_ON); + } + return bitfield; + } + + // TODO: consider using java.util.BitSet + private static int[] bitflagsOf(int bitfield) { + int[] flags = new int[Integer.bitCount(bitfield)]; + int i = 0; + int bitflag = 0; + while (bitfield != 0) { + if ((bitfield & 1) != 0) { + flags[i++] = bitflag; + } + bitflag++; + bitfield = bitfield >>> 1; + } + return flags; + } + + private static String namesOf(int bitfields) { + return Arrays.stream(bitflagsOf(bitfields)) + .mapToObj(i -> Decoder.constants.get(i)) + .collect(Collectors.joining(", ")); + } + + final static class Decoder { + static final SparseArray constants = + MessageUtils.findMessageNames( + new Class[]{ApfProgramEvent.class}, new String[]{"FLAG_"}); + } +} diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java index a39061748ac3..8949fae2545f 100644 --- a/core/java/android/net/metrics/IpManagerEvent.java +++ b/core/java/android/net/metrics/IpManagerEvent.java @@ -29,6 +29,7 @@ import com.android.internal.util.MessageUtils; @SystemApi public final class IpManagerEvent implements Parcelable { + // TODO: use @IntDef public static final int PROVISIONING_OK = 1; public static final int PROVISIONING_FAIL = 2; public static final int COMPLETE_LIFECYCLE = 3; diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java index d5ad0f6c25a9..c2d259fd216b 100644 --- a/core/java/android/net/metrics/ValidationProbeEvent.java +++ b/core/java/android/net/metrics/ValidationProbeEvent.java @@ -29,6 +29,7 @@ import com.android.internal.util.MessageUtils; @SystemApi public final class ValidationProbeEvent implements Parcelable { + // TODO: use @IntDef public static final int PROBE_DNS = 0; public static final int PROBE_HTTP = 1; public static final int PROBE_HTTPS = 2; diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index ce374266b6a2..66fb9005208c 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -24,9 +24,12 @@ import android.net.apf.ApfGenerator; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; import android.net.ip.IpManager; +import android.net.metrics.ApfProgramEvent; +import android.net.metrics.IpConnectivityLog; import android.system.ErrnoException; import android.system.Os; import android.system.PacketSocketAddress; +import android.text.format.DateUtils; import android.util.Log; import android.util.Pair; @@ -140,7 +143,7 @@ public class ApfFilter { // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28; - private static int ARP_HEADER_OFFSET = ETH_HEADER_LEN; + private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{ 0, 1, // Hardware type: Ethernet (1) 8, 0, // Protocol type: IP (0x0800) @@ -148,11 +151,12 @@ public class ApfFilter { 4, // Protocol size: 4 0, 1 // Opcode: request (1) }; - private static int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24; + private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24; private final ApfCapabilities mApfCapabilities; private final IpManager.Callback mIpManagerCallback; private final NetworkInterface mNetworkInterface; + private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); @VisibleForTesting byte[] mHardwareAddress; @VisibleForTesting @@ -213,7 +217,7 @@ public class ApfFilter { // Returns seconds since Unix Epoch. private static long curTime() { - return System.currentTimeMillis() / 1000L; + return System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS; } // A class to hold information about an RA. @@ -760,16 +764,19 @@ public class ApfFilter { return gen; } + /** + * Generate and install a new filter program. + */ @GuardedBy("this") @VisibleForTesting void installNewProgramLocked() { purgeExpiredRasLocked(); + ArrayList rasToFilter = new ArrayList<>(); final byte[] program; long programMinLifetime = Long.MAX_VALUE; try { // Step 1: Determine how many RA filters we can fit in the program. ApfGenerator gen = beginProgramLocked(); - ArrayList rasToFilter = new ArrayList(); for (Ra ra : mRas) { ra.generateFilterLocked(gen); // Stop if we get too big. @@ -797,6 +804,9 @@ public class ApfFilter { hexDump("Installing filter: ", program, program.length); } mIpManagerCallback.installPacketFilter(program); + int flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter); + mMetricsLog.log(new ApfProgramEvent( + programMinLifetime, rasToFilter.size(), mRas.size(), program.length, flags)); } // Install a new filter program if the last installed one will die soon. diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java index 8ac238a9c415..af78839c82d6 100644 --- a/services/tests/servicestests/src/android/net/apf/ApfTest.java +++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java @@ -652,7 +652,7 @@ public class ApfTest extends AndroidTestCase { private static final int DHCP_CLIENT_PORT = 68; private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48; - private static int ARP_HEADER_OFFSET = ETH_HEADER_LEN; + private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{ 0, 1, // Hardware type: Ethernet (1) 8, 0, // Protocol type: IP (0x0800) @@ -660,9 +660,9 @@ public class ApfTest extends AndroidTestCase { 4, // Protocol size: 4 0, 1 // Opcode: request (1) }; - private static int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24; + private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24; - private static byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1}; + private static final byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1}; @LargeTest public void testApfFilterIPv4() throws Exception { -- cgit v1.2.3-59-g8ed1b From 647c86d70a0f2162dcc95854e9dccb925c16ecf3 Mon Sep 17 00:00:00 2001 From: Hugo Benichi Date: Tue, 7 Jun 2016 15:35:16 +0900 Subject: Log RA listening statistics This patch adds a new ApfStats event class that counts RA packet reception statistics on the RA listener thread of ApfFilter and reports the maximum program size advertised by hardware. Statistics are gathered for the lifetime of a network with APF capabilities and uploaded at network teardown when the listener thread exits. Example event: ConnectivityMetricsEvent(15:44:23.741, 0, 0): ApfStats(284945ms 2048B RA: 2 received, 0 matching, 0 ignored, 0 expired, 0 parse errors, 2 program updates) Bug: 28204408 Change-Id: Id2eaafdca97f61152a4b66d06061c971bc0aba4c --- api/system-current.txt | 14 +++ core/java/android/net/metrics/ApfStats.java | 103 +++++++++++++++++++++++ services/net/java/android/net/apf/ApfFilter.java | 98 +++++++++++++++++---- 3 files changed, 200 insertions(+), 15 deletions(-) create mode 100644 core/java/android/net/metrics/ApfStats.java diff --git a/api/system-current.txt b/api/system-current.txt index 77414fb45cb9..41ca0e684b29 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -26031,6 +26031,20 @@ package android.net.metrics { field public final int programLength; } + public final class ApfStats implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public final int droppedRas; + field public final long durationMs; + field public final int matchingRas; + field public final int maxProgramSize; + field public final int parseErrors; + field public final int programUpdates; + field public final int receivedRas; + field public final int zeroLifetimeRas; + } + public final class DefaultNetworkEvent implements android.os.Parcelable { method public int describeContents(); method public static void logEvent(int, int[], int, boolean, boolean); diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java new file mode 100644 index 000000000000..8451e539a7f6 --- /dev/null +++ b/core/java/android/net/metrics/ApfStats.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2016 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.metrics; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An event logged for an interface with APF capabilities when its IpManager state machine exits. + * {@hide} + */ +@SystemApi +public final class ApfStats implements Parcelable { + + public final long durationMs; // time interval in milliseconds these stastistics covers + public final int receivedRas; // number of received RAs + public final int matchingRas; // number of received RAs matching a known RA + public final int droppedRas; // number of received RAs ignored due to the MAX_RAS limit + public final int zeroLifetimeRas; // number of received RAs with a minimum lifetime of 0 + public final int parseErrors; // number of received RAs that could not be parsed + public final int programUpdates; // number of APF program updates + public final int maxProgramSize; // maximum APF program size advertised by hardware + + /** {@hide} */ + public ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas, + int zeroLifetimeRas, int parseErrors, int programUpdates, int maxProgramSize) { + this.durationMs = durationMs; + this.receivedRas = receivedRas; + this.matchingRas = matchingRas; + this.droppedRas = droppedRas; + this.zeroLifetimeRas = zeroLifetimeRas; + this.parseErrors = parseErrors; + this.programUpdates = programUpdates; + this.maxProgramSize = maxProgramSize; + } + + private ApfStats(Parcel in) { + this.durationMs = in.readLong(); + this.receivedRas = in.readInt(); + this.matchingRas = in.readInt(); + this.droppedRas = in.readInt(); + this.zeroLifetimeRas = in.readInt(); + this.parseErrors = in.readInt(); + this.programUpdates = in.readInt(); + this.maxProgramSize = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(durationMs); + out.writeInt(receivedRas); + out.writeInt(matchingRas); + out.writeInt(droppedRas); + out.writeInt(zeroLifetimeRas); + out.writeInt(parseErrors); + out.writeInt(programUpdates); + out.writeInt(maxProgramSize); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return new StringBuilder("ApfStats(") + .append(String.format("%dms ", durationMs)) + .append(String.format("%dB RA: {", maxProgramSize)) + .append(String.format("%d received, ", receivedRas)) + .append(String.format("%d matching, ", matchingRas)) + .append(String.format("%d dropped, ", droppedRas)) + .append(String.format("%d zero lifetime, ", zeroLifetimeRas)) + .append(String.format("%d parse errors, ", parseErrors)) + .append(String.format("%d program updates})", programUpdates)) + .toString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ApfStats createFromParcel(Parcel in) { + return new ApfStats(in); + } + + public ApfStats[] newArray(int size) { + return new ApfStats[size]; + } + }; +} diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 66fb9005208c..6d1a4eb40021 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -18,6 +18,7 @@ package android.net.apf; import static android.system.OsConstants.*; +import android.os.SystemClock; import android.net.LinkProperties; import android.net.NetworkUtils; import android.net.apf.ApfGenerator; @@ -25,6 +26,7 @@ import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; import android.net.ip.IpManager; import android.net.metrics.ApfProgramEvent; +import android.net.metrics.ApfStats; import android.net.metrics.IpConnectivityLog; import android.system.ErrnoException; import android.system.Os; @@ -72,6 +74,17 @@ import libcore.io.IoBridge; * @hide */ public class ApfFilter { + + // Enums describing the outcome of receiving an RA packet. + private static enum ProcessRaResult { + MATCH, // Received RA matched a known RA + DROPPED, // Received RA ignored due to MAX_RAS + PARSE_ERROR, // Received RA could not be parsed + ZERO_LIFETIME, // Received RA had 0 lifetime + UPDATE_NEW_RA, // APF program updated for new RA + UPDATE_EXPIRY // APF program updated for expiry + } + // Thread to listen for RAs. @VisibleForTesting class ReceiveThread extends Thread { @@ -79,6 +92,16 @@ public class ApfFilter { private final FileDescriptor mSocket; private volatile boolean mStopped; + // Starting time of the RA receiver thread. + private final long mStart = SystemClock.elapsedRealtime(); + + private int mReceivedRas; // Number of received RAs + private int mMatchingRas; // Number of received RAs matching a known RA + private int mDroppedRas; // Number of received RAs ignored due to the MAX_RAS limit + private int mParseErrors; // Number of received RAs that could not be parsed + private int mZeroLifetimeRas; // Number of received RAs with a 0 lifetime + private int mProgramUpdates; // Number of APF program updates triggered by receiving a RA + public ReceiveThread(FileDescriptor socket) { mSocket = socket; } @@ -97,13 +120,46 @@ public class ApfFilter { while (!mStopped) { try { int length = Os.read(mSocket, mPacket, 0, mPacket.length); - processRa(mPacket, length); + updateStats(processRa(mPacket, length)); } catch (IOException|ErrnoException e) { if (!mStopped) { Log.e(TAG, "Read error", e); } } } + logStats(); + } + + private void updateStats(ProcessRaResult result) { + mReceivedRas++; + switch(result) { + case MATCH: + mMatchingRas++; + return; + case DROPPED: + mDroppedRas++; + return; + case PARSE_ERROR: + mParseErrors++; + return; + case ZERO_LIFETIME: + mZeroLifetimeRas++; + return; + case UPDATE_EXPIRY: + mMatchingRas++; + mProgramUpdates++; + return; + case UPDATE_NEW_RA: + mProgramUpdates++; + return; + } + } + + private void logStats() { + long durationMs = SystemClock.elapsedRealtime() - mStart; + int maxSize = mApfCapabilities.maximumApfProgramSize; + mMetricsLog.log(new ApfStats(durationMs, mReceivedRas, mMatchingRas, mDroppedRas, + mZeroLifetimeRas, mParseErrors, mProgramUpdates, maxSize)); } } @@ -216,6 +272,7 @@ public class ApfFilter { } // Returns seconds since Unix Epoch. + // TODO: use SystemClock.elapsedRealtime() instead private static long curTime() { return System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS; } @@ -809,15 +866,12 @@ public class ApfFilter { programMinLifetime, rasToFilter.size(), mRas.size(), program.length, flags)); } - // Install a new filter program if the last installed one will die soon. - @GuardedBy("this") - private void maybeInstallNewProgramLocked() { - if (mRas.size() == 0) return; - // If the current program doesn't expire for a while, don't bother updating. + /** + * Returns {@code true} if a new program should be installed because the current one dies soon. + */ + private boolean shouldInstallnewProgram() { long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime; - if (expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING) { - installNewProgramLocked(); - } + return expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING; } private void hexDump(String msg, byte[] packet, int length) { @@ -836,7 +890,12 @@ public class ApfFilter { } } - private synchronized void processRa(byte[] packet, int length) { + /** + * Process an RA packet, updating the list of known RAs and installing a new APF program + * if the current APF program should be updated. + * @return a ProcessRaResult enum describing what action was performed. + */ + private synchronized ProcessRaResult processRa(byte[] packet, int length) { if (VDBG) hexDump("Read packet = ", packet, length); // Have we seen this RA before? @@ -858,25 +917,34 @@ public class ApfFilter { // Swap to front of array. mRas.add(0, mRas.remove(i)); - maybeInstallNewProgramLocked(); - return; + // If the current program doesn't expire for a while, don't update. + if (shouldInstallnewProgram()) { + installNewProgramLocked(); + return ProcessRaResult.UPDATE_EXPIRY; + } + return ProcessRaResult.MATCH; } } purgeExpiredRasLocked(); // TODO: figure out how to proceed when we've received more then MAX_RAS RAs. - if (mRas.size() >= MAX_RAS) return; + if (mRas.size() >= MAX_RAS) { + return ProcessRaResult.DROPPED; + } final Ra ra; try { ra = new Ra(packet, length); } catch (Exception e) { Log.e(TAG, "Error parsing RA: " + e); - return; + return ProcessRaResult.PARSE_ERROR; } // Ignore 0 lifetime RAs. - if (ra.isExpired()) return; + if (ra.isExpired()) { + return ProcessRaResult.ZERO_LIFETIME; + } log("Adding " + ra); mRas.add(ra); installNewProgramLocked(); + return ProcessRaResult.UPDATE_NEW_RA; } /** -- cgit v1.2.3-59-g8ed1b From cfbf7414a14cf91d1b5c83154aab54c32d6be76a Mon Sep 17 00:00:00 2001 From: Hugo Benichi Date: Thu, 23 Jun 2016 10:41:30 +0900 Subject: Record events for RA option lifetimes This patch defines a new android.net.metrics.RaEvent class carrying lifetime values contained in RA packets. RaEvent are recorded when ApfFilter processes a new RA for which there is no match. Example: ConnectivityMetricsEvent(15:39:39.808, 0, 0): RaEvent(lifetimes: router=3600s, prefix_valid=2592000s, prefix_preferred=604800s, route_info=-1s, dnssl=-1s, rdnss=3600s) Change-Id: Ia28652e03ed442d5f2a686ef5b3fafbcb77c503a --- api/system-current.txt | 12 +++ core/java/android/net/metrics/RaEvent.java | 95 ++++++++++++++++++++++++ services/net/java/android/net/apf/ApfFilter.java | 77 +++++++++++++------ 3 files changed, 161 insertions(+), 23 deletions(-) create mode 100644 core/java/android/net/metrics/RaEvent.java diff --git a/api/system-current.txt b/api/system-current.txt index 41ca0e684b29..769f8678f608 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -26153,6 +26153,18 @@ package android.net.metrics { field public final int netId; } + public final class RaEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public final long dnsslLifetime; + field public final long prefixPreferredLifetime; + field public final long prefixValidLifetime; + field public final long rdnssLifetime; + field public final long routeInfoLifetime; + field public final long routerLifetime; + } + public final class ValidationProbeEvent implements android.os.Parcelable { method public int describeContents(); method public static void logEvent(int, long, int, int); diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java new file mode 100644 index 000000000000..69013c0de637 --- /dev/null +++ b/core/java/android/net/metrics/RaEvent.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 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.metrics; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * An event logged when the APF packet socket receives an RA packet. + * {@hide} + */ +@SystemApi +public final class RaEvent implements Parcelable { + + // Lifetime in seconds of options found in a single RA packet. + // When an option is not set, the value of the associated field is -1; + public final long routerLifetime; + public final long prefixValidLifetime; + public final long prefixPreferredLifetime; + public final long routeInfoLifetime; + public final long rdnssLifetime; + public final long dnsslLifetime; + + /** {@hide} */ + public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime, + long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) { + this.routerLifetime = routerLifetime; + this.prefixValidLifetime = prefixValidLifetime; + this.prefixPreferredLifetime = prefixPreferredLifetime; + this.routeInfoLifetime = routeInfoLifetime; + this.rdnssLifetime = rdnssLifetime; + this.dnsslLifetime = dnsslLifetime; + } + + private RaEvent(Parcel in) { + routerLifetime = in.readLong(); + prefixValidLifetime = in.readLong(); + prefixPreferredLifetime = in.readLong(); + routeInfoLifetime = in.readLong(); + rdnssLifetime = in.readLong(); + dnsslLifetime = in.readLong(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(routerLifetime); + out.writeLong(prefixValidLifetime); + out.writeLong(prefixPreferredLifetime); + out.writeLong(routeInfoLifetime); + out.writeLong(rdnssLifetime); + out.writeLong(dnsslLifetime); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return new StringBuilder("RaEvent(lifetimes: ") + .append(String.format("router=%ds, ", routerLifetime)) + .append(String.format("prefix_valid=%ds, ", prefixValidLifetime)) + .append(String.format("prefix_preferred=%ds, ", prefixPreferredLifetime)) + .append(String.format("route_info=%ds, ", routeInfoLifetime)) + .append(String.format("rdnss=%ds, ", rdnssLifetime)) + .append(String.format("dnssl=%ds)", dnsslLifetime)) + .toString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public RaEvent createFromParcel(Parcel in) { + return new RaEvent(in); + } + + public RaEvent[] newArray(int size) { + return new RaEvent[size]; + } + }; +} diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 6d1a4eb40021..0ef9d7abaa38 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -28,6 +28,7 @@ import android.net.ip.IpManager; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; import android.net.metrics.IpConnectivityLog; +import android.net.metrics.RaEvent; import android.system.ErrnoException; import android.system.Os; import android.system.PacketSocketAddress; @@ -357,7 +358,7 @@ public class ApfFilter { } // Can't be static because it's in a non-static inner class. - // TODO: Make this final once RA is its own class. + // TODO: Make this static once RA is its own class. private int uint8(byte b) { return b & 0xff; } @@ -366,8 +367,8 @@ public class ApfFilter { return s & 0xffff; } - private long uint32(int s) { - return s & 0xffffffff; + private long uint32(int i) { + return i & 0xffffffffL; } private void prefixOptionToString(StringBuffer sb, int offset) { @@ -427,6 +428,11 @@ public class ApfFilter { return lifetimeOffset + lifetimeLength; } + private int addNonLifetimeU32(int lastNonLifetimeStart) { + return addNonLifetime(lastNonLifetimeStart, + ICMP6_4_BYTE_LIFETIME_OFFSET, ICMP6_4_BYTE_LIFETIME_LEN); + } + // Note that this parses RA and may throw IllegalArgumentException (from // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with @@ -446,11 +452,20 @@ public class ApfFilter { ICMP6_RA_ROUTER_LIFETIME_OFFSET, ICMP6_RA_ROUTER_LIFETIME_LEN); + long routerLifetime = uint16(mPacket.getShort( + ICMP6_RA_ROUTER_LIFETIME_OFFSET + mPacket.position())); + long prefixValidLifetime = -1L; + long prefixPreferredLifetime = -1L; + long routeInfoLifetime = -1L; + long dnsslLifetime = - 1L; + long rdnssLifetime = -1L; + // Ensures that the RA is not truncated. mPacket.position(ICMP6_RA_OPTION_OFFSET); while (mPacket.hasRemaining()) { - int optionType = ((int)mPacket.get(mPacket.position())) & 0xff; - int optionLength = (((int)mPacket.get(mPacket.position() + 1)) & 0xff) * 8; + final int position = mPacket.position(); + final int optionType = uint8(mPacket.get(position)); + final int optionLength = uint8(mPacket.get(position + 1)) * 8; switch (optionType) { case ICMP6_PREFIX_OPTION_TYPE: // Parse valid lifetime @@ -461,19 +476,29 @@ public class ApfFilter { lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart, ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET, ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN); - mPrefixOptionOffsets.add(mPacket.position()); + mPrefixOptionOffsets.add(position); + prefixValidLifetime = uint32(mPacket.getInt( + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET + position)); + prefixPreferredLifetime = uint32(mPacket.getInt( + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET + position)); break; - // These three options have the same lifetime offset and size, so process - // together: + // These three options have the same lifetime offset and size, and + // are processed with the same specialized addNonLifetime4B: case ICMP6_RDNSS_OPTION_TYPE: - mRdnssOptionOffsets.add(mPacket.position()); - // Fall through. + mRdnssOptionOffsets.add(position); + lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart); + rdnssLifetime = + uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position)); + break; case ICMP6_ROUTE_INFO_OPTION_TYPE: + lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart); + routeInfoLifetime = + uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position)); + break; case ICMP6_DNSSL_OPTION_TYPE: - // Parse lifetime - lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart, - ICMP6_4_BYTE_LIFETIME_OFFSET, - ICMP6_4_BYTE_LIFETIME_LEN); + lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart); + dnsslLifetime = + uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position)); break; default: // RFC4861 section 4.2 dictates we ignore unknown options for fowards @@ -484,11 +509,14 @@ public class ApfFilter { throw new IllegalArgumentException(String.format( "Invalid option length opt=%d len=%d", optionType, optionLength)); } - mPacket.position(mPacket.position() + optionLength); + mPacket.position(position + optionLength); } // Mark non-lifetime bytes since last lifetime. addNonLifetime(lastNonLifetimeStart, 0, 0); mMinLifetime = minLifetime(packet, length); + // TODO: record per-option minimum lifetimes instead of last seen lifetimes + mMetricsLog.log(new RaEvent(routerLifetime, prefixValidLifetime, + prefixPreferredLifetime, routeInfoLifetime, rdnssLifetime, dnsslLifetime)); } // Ignoring lifetimes (which may change) does {@code packet} match this RA? @@ -517,16 +545,19 @@ public class ApfFilter { continue; } - int lifetimeLength = mNonLifetimes.get(i+1).first - offset; - long val; + final int lifetimeLength = mNonLifetimes.get(i+1).first - offset; + final long optionLifetime; switch (lifetimeLength) { - case 2: val = byteBuffer.getShort(offset); break; - case 4: val = byteBuffer.getInt(offset); break; - default: throw new IllegalStateException("bogus lifetime size " + length); + case 2: + optionLifetime = uint16(byteBuffer.getShort(offset)); + break; + case 4: + optionLifetime = uint32(byteBuffer.getInt(offset)); + break; + default: + throw new IllegalStateException("bogus lifetime size " + lifetimeLength); } - // Mask to size, converting signed to unsigned - val &= (1L << (lifetimeLength * 8)) - 1; - minLifetime = Math.min(minLifetime, val); + minLifetime = Math.min(minLifetime, optionLifetime); } return minLifetime; } -- cgit v1.2.3-59-g8ed1b