From 40b43542c1845f80de07f0b2b391ad4bdaa82129 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Mon, 16 Apr 2018 20:15:12 +0900 Subject: [PATCH 1/2] Add support for reading a snapshot of the APF data No functional change yet, since startReadPacketFilter() has no callers at this time. In the future, this new hook will be used to take periodic snapshots of the APF memory (for instance, when the device wakes up). Design note: WifiStateMachine grabs the APF data synchronously from another thread, but then the data snapshot is delivered to IpClient via an asynchronous reply, following the same pattern used by other commands. This means that there's no (practical) way for IpClient to read the APF data just before replacing the APF program. Even with this limitation, it's still possible to reliably decode packet counters and compute deltas relative to the last snapshot, provided that the address range isn't cleared when installing a new APF filter. Bug: 73804303 Test: Manual - called the new code and inspected 'dumpsys wifi' output. Change-Id: Ia0923d71cf3ee4128fb1c381557316300adac1a3 Merged-In: Ia0923d71cf3ee4128fb1c381557316300adac1a3 Merged-In: I3b940f5a3b795f85d244882eaa7eca56bd9e167d Merged-In: I283fd5fb71f8a679911e58c487a4ac12a5190049 (cherry picked from commit bb2193bf5877b7a93aebefdb74a8c7f410a17907) --- .../net/java/android/net/ip/IpClient.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 37bb2ad8f6c1e..02bd787ba0bdc 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -16,6 +16,7 @@ package android.net.ip; +import com.android.internal.util.HexDump; import com.android.internal.util.MessageUtils; import com.android.internal.util.WakeupMessage; @@ -174,6 +175,12 @@ public class IpClient extends StateMachine { // Install an APF program to filter incoming packets. public void installPacketFilter(byte[] filter) {} + // Asynchronously read back the APF program & data buffer from the wifi driver. + // Due to Wifi HAL limitations, the current implementation only supports dumping the entire + // buffer. In response to this request, the driver returns the data buffer asynchronously + // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message. + public void startReadPacketFilter() {} + // If multicast filtering cannot be accomplished with APF, this function will be called to // actuate multicast filtering using another means. public void setFallbackMulticastFilter(boolean enabled) {} @@ -280,6 +287,11 @@ public class IpClient extends StateMachine { log("installPacketFilter(byte[" + filter.length + "])"); } @Override + public void startReadPacketFilter() { + mCallback.startReadPacketFilter(); + log("startReadPacketFilter()"); + } + @Override public void setFallbackMulticastFilter(boolean enabled) { mCallback.setFallbackMulticastFilter(enabled); log("setFallbackMulticastFilter(" + enabled + ")"); @@ -591,6 +603,7 @@ public class IpClient extends StateMachine { private static final int CMD_SET_MULTICAST_FILTER = 9; private static final int EVENT_PROVISIONING_TIMEOUT = 10; private static final int EVENT_DHCPACTION_TIMEOUT = 11; + private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; private static final int MAX_LOG_RECORDS = 500; private static final int MAX_PACKET_RECORDS = 100; @@ -643,6 +656,7 @@ public class IpClient extends StateMachine { private ApfFilter mApfFilter; private boolean mMulticastFiltering; private long mStartTimeMillis; + private byte[] mApfDataSnapshot; public static class Dependencies { public INetworkManagementService getNMS() { @@ -857,6 +871,10 @@ public class IpClient extends StateMachine { sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); } + public void readPacketFilterComplete(byte[] data) { + sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data); + } + /** * Set the TCP buffer sizes to use. * @@ -897,6 +915,7 @@ public class IpClient extends StateMachine { final ProvisioningConfiguration provisioningConfig = mConfiguration; final ApfCapabilities apfCapabilities = (provisioningConfig != null) ? provisioningConfig.mApfCapabilities : null; + final byte[] apfDataSnapshot = mApfDataSnapshot; IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); pw.println(mTag + " APF dump:"); @@ -914,6 +933,14 @@ public class IpClient extends StateMachine { } } pw.decreaseIndent(); + pw.println(mTag + " latest APF data snapshot: "); + pw.increaseIndent(); + if (apfDataSnapshot != null) { + pw.println(HexDump.dumpHexString(apfDataSnapshot)); + } else { + pw.println("No last snapshot."); + } + pw.decreaseIndent(); pw.println(); pw.println(mTag + " current ProvisioningConfiguration:"); @@ -1710,6 +1737,11 @@ public class IpClient extends StateMachine { break; } + case EVENT_READ_PACKET_FILTER_COMPLETE: { + mApfDataSnapshot = (byte[]) msg.obj; + break; + } + case EVENT_DHCPACTION_TIMEOUT: stopDhcpAction(); break; From ab30db7072968016c1be0bb384385fd7235ae815 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Tue, 24 Apr 2018 01:25:42 +0900 Subject: [PATCH 2/2] apf: Add counters for dropped / passed packets ApfFilter maintains separate counters for each reason why a packet was passed or dropped by the filter logic. There's also a total which should match the individual counters, *unless* the APF interpreter aborted execution early due to an illegal instruction or an out-of-bounds access. Test: both on APFv2 and APFv4-capable device: runtest -x tests/net/java/android/net/ip/IpClientTest.java runtest -x tests/net/java/android/net/apf/ApfTest.java manual tests connected to an AP Bug: 73804303 Change-Id: I54b17fcbb95dfaea5db975d282314ce73d79d6ec Merged-In: I54b17fcbb95dfaea5db975d282314ce73d79d6ec (cherry picked from commit 3cc40ea6c50c976dd4e6485574a8be899867f610) --- .../java/android/net/apf/ApfCapabilities.java | 10 + .../net/java/android/net/apf/ApfFilter.java | 276 ++++++++++++++++-- .../java/android/net/apf/ApfGenerator.java | 5 +- .../net/java/android/net/ip/IpClient.java | 33 ++- 4 files changed, 280 insertions(+), 44 deletions(-) diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java index 703b415602908..dec8ca2073431 100644 --- a/services/net/java/android/net/apf/ApfCapabilities.java +++ b/services/net/java/android/net/apf/ApfCapabilities.java @@ -49,4 +49,14 @@ public class ApfCapabilities { return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(), apfVersionSupported, maximumApfProgramSize, apfPacketFormat); } + + /** + * Returns true if the APF interpreter advertises support for the data buffer access opcodes + * LDDW and STDW. + * + * Full LDDW and STDW support is present from APFv4 on. + */ + public boolean hasDataAccess() { + return apfVersionSupported >= 4; + } } diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 92d37096d5a69..8ac6ede03529e 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -24,6 +24,7 @@ import static com.android.internal.util.BitUtils.getUint32; import static com.android.internal.util.BitUtils.getUint8; import static com.android.internal.util.BitUtils.uint32; +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -102,6 +103,70 @@ public class ApfFilter { UPDATE_EXPIRY // APF program updated for expiry } + /** + * APF packet counters. + * + * Packet counters are 32bit big-endian values, and allocated near the end of the APF data + * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4, + * the last writable 32bit word. + */ + @VisibleForTesting + private static enum Counter { + RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds) + TOTAL_PACKETS, + PASSED_ARP, + PASSED_DHCP, + PASSED_IPV4, + PASSED_IPV6_NON_ICMP, + PASSED_IPV4_UNICAST, + PASSED_IPV6_ICMP, + PASSED_IPV6_UNICAST_NON_ICMP, + PASSED_ARP_NON_IPV4, + PASSED_ARP_UNKNOWN, + PASSED_ARP_UNICAST_REPLY, + PASSED_NON_IP_UNICAST, + DROPPED_ETH_BROADCAST, + DROPPED_RA, + DROPPED_GARP_REPLY, + DROPPED_ARP_OTHER_HOST, + DROPPED_IPV4_L2_BROADCAST, + DROPPED_IPV4_BROADCAST_ADDR, + DROPPED_IPV4_BROADCAST_NET, + DROPPED_IPV4_MULTICAST, + DROPPED_IPV6_ROUTER_SOLICITATION, + DROPPED_IPV6_MULTICAST_NA, + DROPPED_IPV6_MULTICAST, + DROPPED_IPV6_MULTICAST_PING, + DROPPED_IPV6_NON_ICMP_MULTICAST, + DROPPED_802_3_FRAME, + DROPPED_ETHERTYPE_BLACKLISTED; + + // Returns the negative byte offset from the end of the APF data segment for + // a given counter. + public int offset() { + return - this.ordinal() * 4; // Currently, all counters are 32bit long. + } + + // Returns the total size of the data segment in bytes. + public static int totalSize() { + return (Counter.class.getEnumConstants().length - 1) * 4; + } + } + + /** + * When APFv4 is supported, loads R1 with the offset of the specified counter. + */ + private void maybeSetCounter(ApfGenerator gen, Counter c) { + if (mApfCapabilities.hasDataAccess()) { + gen.addLoadImmediate(Register.R1, c.offset()); + } + } + + // When APFv4 is supported, these point to the trampolines generated by emitEpilogue(). + // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL. + private final String mCountAndPassLabel; + private final String mCountAndDropLabel; + // Thread to listen for RAs. @VisibleForTesting class ReceiveThread extends Thread { @@ -289,6 +354,16 @@ public class ApfFilter { mDrop802_3Frames = config.ieee802_3Filter; mContext = context; + if (mApfCapabilities.hasDataAccess()) { + mCountAndPassLabel = "countAndPass"; + mCountAndDropLabel = "countAndDrop"; + } else { + // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP, + // preserving the original pre-APFv4 behavior. + mCountAndPassLabel = ApfGenerator.PASS_LABEL; + mCountAndDropLabel = ApfGenerator.DROP_LABEL; + } + // Now fill the black list from the passed array mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList); @@ -302,6 +377,10 @@ public class ApfFilter { new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); } + public synchronized void setDataSnapshot(byte[] data) { + mDataSnapshot = data; + } + private void log(String s) { Log.d(TAG, "(" + mInterfaceParams.name + "): " + s); } @@ -350,6 +429,10 @@ public class ApfFilter { try { mHardwareAddress = mInterfaceParams.macAddr.toByteArray(); synchronized(this) { + // Clear APF memory. + byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize]; + mIpClientCallback.installPacketFilter(zeroes); + // Install basic filters installNewProgramLocked(); } @@ -729,7 +812,8 @@ public class ApfFilter { gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel); } } - gen.addJump(gen.DROP_LABEL); + maybeSetCounter(gen, Counter.DROPPED_RA); + gen.addJump(mCountAndDropLabel); gen.defineLabel(nextFilterLabel); return filterLifetime; } @@ -764,6 +848,16 @@ public class ApfFilter { @GuardedBy("this") private byte[] mLastInstalledProgram; + /** + * For debugging only. Contains the latest APF buffer snapshot captured from the firmware. + * + * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports + * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for + * the opcodes to access the data buffer (LDDW and STDW). + */ + @GuardedBy("this") @Nullable + private byte[] mDataSnapshot; + // How many times the program was updated since we started. @GuardedBy("this") private int mNumProgramUpdates = 0; @@ -799,31 +893,37 @@ public class ApfFilter { // Pass if not ARP IPv4. gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_ARP_NON_IPV4); + gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel); // Pass if unknown ARP opcode. gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET); gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check - gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_ARP_UNKNOWN); + gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel); // Pass if unicast reply. gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY); + gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel); // Either a unicast request, a unicast reply, or a broadcast reply. gen.defineLabel(checkTargetIPv4); if (mIPv4Address == null) { // When there is no IPv4 address, drop GARP replies (b/29404209). gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET); - gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, gen.DROP_LABEL); + maybeSetCounter(gen, Counter.DROPPED_GARP_REPLY); + gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel); } else { // When there is an IPv4 address, drop unicast/broadcast requests // and broadcast replies with a different target IPv4 address. gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, gen.DROP_LABEL); + maybeSetCounter(gen, Counter.DROPPED_ARP_OTHER_HOST); + gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel); } - gen.addJump(gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_ARP); + gen.addJump(mCountAndPassLabel); } /** @@ -866,7 +966,8 @@ public class ApfFilter { // NOTE: Relies on R1 containing IPv4 header offset. gen.addAddR1(); gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter); - gen.addJump(gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_DHCP); + gen.addJump(mCountAndPassLabel); // Drop all multicasts/broadcasts. gen.defineLabel(skipDhcpv4Filter); @@ -874,24 +975,31 @@ public class ApfFilter { // If IPv4 destination address is in multicast range, drop. gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET); gen.addAnd(0xf0); - gen.addJumpIfR0Equals(0xe0, gen.DROP_LABEL); + maybeSetCounter(gen, Counter.DROPPED_IPV4_MULTICAST); + gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel); // If IPv4 broadcast packet, drop regardless of L2 (b/30231088). + maybeSetCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR); gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET); - gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, gen.DROP_LABEL); + gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel); if (mIPv4Address != null && mIPv4PrefixLength < 31) { + maybeSetCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET); int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength); - gen.addJumpIfR0Equals(broadcastAddr, gen.DROP_LABEL); + gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); } // If L2 broadcast packet, drop. + // TODO: can we invert this condition to fall through to the common pass case below? + maybeSetCounter(gen, Counter.PASSED_IPV4_UNICAST); gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL); - gen.addJump(gen.DROP_LABEL); + gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel); + maybeSetCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); + gen.addJump(mCountAndDropLabel); } // Otherwise, pass - gen.addJump(gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_IPV4); + gen.addJump(mCountAndPassLabel); } @@ -938,14 +1046,17 @@ public class ApfFilter { // Drop all other packets sent to ff00::/8 (multicast prefix). gen.defineLabel(dropAllIPv6MulticastsLabel); + maybeSetCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST); gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET); - gen.addJumpIfR0Equals(0xff, gen.DROP_LABEL); + gen.addJumpIfR0Equals(0xff, mCountAndDropLabel); // Not multicast. Pass. - gen.addJump(gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP); + gen.addJump(mCountAndPassLabel); gen.defineLabel(skipIPv6MulticastFilterLabel); } else { // If not ICMPv6, pass. - gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, gen.PASS_LABEL); + maybeSetCounter(gen, Counter.PASSED_IPV6_NON_ICMP); + gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel); } // If we got this far, the packet is ICMPv6. Drop some specific types. @@ -954,7 +1065,8 @@ public class ApfFilter { String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA"; gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET); // Drop all router solicitations (b/32833400) - gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, gen.DROP_LABEL); + maybeSetCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION); + gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel); // If not neighbor announcements, skip filter. gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel); // If to ff02::1, drop. @@ -962,7 +1074,8 @@ public class ApfFilter { gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET); gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS, skipUnsolicitedMulticastNALabel); - gen.addJump(gen.DROP_LABEL); + maybeSetCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); + gen.addJump(mCountAndDropLabel); gen.defineLabel(skipUnsolicitedMulticastNALabel); } @@ -985,10 +1098,18 @@ public class ApfFilter { * */ @GuardedBy("this") - private ApfGenerator beginProgramLocked() throws IllegalInstructionException { + private ApfGenerator emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported); + if (mApfCapabilities.hasDataAccess()) { + // Increment TOTAL_PACKETS + maybeSetCounter(gen, Counter.TOTAL_PACKETS); + gen.addLoadData(Register.R0, 0); // load counter + gen.addAdd(1); + gen.addStoreData(Register.R0, 0); // write-back counter + } + // Here's a basic summary of what the initial program does: // // if it's a 802.3 Frame (ethtype < 0x0600): @@ -1009,12 +1130,14 @@ public class ApfFilter { if (mDrop802_3Frames) { // drop 802.3 frames (ethtype < 0x0600) - gen.addJumpIfR0LessThan(ETH_TYPE_MIN, gen.DROP_LABEL); + maybeSetCounter(gen, Counter.DROPPED_802_3_FRAME); + gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel); } // Handle ether-type black list + maybeSetCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED); for (int p : mEthTypeBlackList) { - gen.addJumpIfR0Equals(p, gen.DROP_LABEL); + gen.addJumpIfR0Equals(p, mCountAndDropLabel); } // Add ARP filters: @@ -1041,8 +1164,10 @@ public class ApfFilter { // Drop non-IP non-ARP broadcasts, pass the rest gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL); - gen.addJump(gen.DROP_LABEL); + maybeSetCounter(gen, Counter.PASSED_NON_IP_UNICAST); + gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel); + maybeSetCounter(gen, Counter.DROPPED_ETH_BROADCAST); + gen.addJump(mCountAndDropLabel); // Add IPv6 filters: gen.defineLabel(ipv6FilterLabel); @@ -1050,6 +1175,39 @@ public class ApfFilter { return gen; } + /** + * Append packet counting epilogue to the APF program. + * + * Currently, the epilogue consists of two trampolines which count passed and dropped packets + * before jumping to the actual PASS and DROP labels. + */ + @GuardedBy("this") + private void emitEpilogue(ApfGenerator gen) throws IllegalInstructionException { + // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it + // will just fall-through to the PASS label. + if (!mApfCapabilities.hasDataAccess()) return; + + // Execution will reach the bottom of the program if none of the filters match, + // which will pass the packet to the application processor. + maybeSetCounter(gen, Counter.PASSED_IPV6_ICMP); + + // Append the count & pass trampoline, which increments the counter at the data address + // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting + // the entire sequence inline for every counter. + gen.defineLabel(mCountAndPassLabel); + gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0) + gen.addAdd(1); // R0++ + gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0 + gen.addJump(gen.PASS_LABEL); + + // Same as above for the count & drop trampoline. + gen.defineLabel(mCountAndDropLabel); + gen.addLoadData(Register.R0, 0); // R0 = *(R1 + 0) + gen.addAdd(1); // R0++ + gen.addStoreData(Register.R0, 0); // *(R1 + 0) = R0 + gen.addJump(gen.DROP_LABEL); + } + /** * Generate and install a new filter program. */ @@ -1060,22 +1218,39 @@ public class ApfFilter { ArrayList rasToFilter = new ArrayList<>(); final byte[] program; long programMinLifetime = Long.MAX_VALUE; + long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize; + if (mApfCapabilities.hasDataAccess()) { + // Reserve space for the counters. + maximumApfProgramSize -= Counter.totalSize(); + } + try { // Step 1: Determine how many RA filters we can fit in the program. - ApfGenerator gen = beginProgramLocked(); + ApfGenerator gen = emitPrologueLocked(); + + // The epilogue normally goes after the RA filters, but add it early to include its + // length when estimating the total. + emitEpilogue(gen); + + // Can't fit the program even without any RA filters? + if (gen.programLengthOverEstimate() > maximumApfProgramSize) { + Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize); + return; + } + for (Ra ra : mRas) { ra.generateFilterLocked(gen); // Stop if we get too big. - if (gen.programLengthOverEstimate() > mApfCapabilities.maximumApfProgramSize) break; + if (gen.programLengthOverEstimate() > maximumApfProgramSize) break; rasToFilter.add(ra); } + // Step 2: Actually generate the program - gen = beginProgramLocked(); + gen = emitPrologueLocked(); for (Ra ra : rasToFilter) { programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen)); } - // Execution will reach the end of the program if no filters match, which will pass the - // packet to the AP. + emitEpilogue(gen); program = gen.generate(); } catch (IllegalInstructionException|IllegalStateException e) { Log.e(TAG, "Failed to generate APF program.", e); @@ -1277,6 +1452,23 @@ public class ApfFilter { installNewProgramLocked(); } + static public long counterValue(byte[] data, Counter counter) + throws ArrayIndexOutOfBoundsException { + // Follow the same wrap-around addressing scheme of the interpreter. + int offset = counter.offset(); + if (offset < 0) { + offset = data.length + offset; + } + + // Decode 32bit big-endian integer into a long so we can count up beyond 2^31. + long value = 0; + for (int i = 0; i < 4; i++) { + value = value << 8 | (data[offset] & 0xFF); + offset++; + } + return value; + } + public synchronized void dump(IndentingPrintWriter pw) { pw.println("Capabilities: " + mApfCapabilities); pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED")); @@ -1318,6 +1510,32 @@ public class ApfFilter { pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */)); pw.decreaseIndent(); } + + pw.println("APF packet counters: "); + pw.increaseIndent(); + if (!mApfCapabilities.hasDataAccess()) { + pw.println("APF counters not supported"); + } else if (mDataSnapshot == null) { + pw.println("No last snapshot."); + } else { + try { + Counter[] counters = Counter.class.getEnumConstants(); + for (Counter c : Arrays.asList(counters).subList(1, counters.length)) { + long value = counterValue(mDataSnapshot, c); + // Only print non-zero counters + if (value != 0) { + pw.println(c.toString() + ": " + value); + } + } + } catch (ArrayIndexOutOfBoundsException e) { + pw.println("Uh-oh: " + e); + } + if (VDBG) { + pw.println("Raw data dump: "); + pw.println(HexDump.dumpHexString(mDataSnapshot)); + } + } + pw.decreaseIndent(); } // TODO: move to android.net.NetworkUtils diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/services/net/java/android/net/apf/ApfGenerator.java index 99b2fc6db4729..87a1b5ea8b4d9 100644 --- a/services/net/java/android/net/apf/ApfGenerator.java +++ b/services/net/java/android/net/apf/ApfGenerator.java @@ -378,8 +378,7 @@ public class ApfGenerator { } /** - * Returns true if the specified {@code version} is supported by the ApfGenerator, otherwise - * false. + * Returns true if the ApfGenerator supports the specified {@code version}, otherwise false. */ public static boolean supportsVersion(int version) { return version >= MIN_APF_VERSION; @@ -753,7 +752,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to jump to {@code target} if the bytes of the - * packet at, an offset specified by {@code register}, match {@code bytes}. + * packet at an offset specified by {@code register} match {@code bytes}. */ public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target) throws IllegalInstructionException { diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 02bd787ba0bdc..63ae09a79379e 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -656,7 +656,14 @@ public class IpClient extends StateMachine { private ApfFilter mApfFilter; private boolean mMulticastFiltering; private long mStartTimeMillis; - private byte[] mApfDataSnapshot; + + /** + * Reading the snapshot is an asynchronous operation initiated by invoking + * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an + * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable + * signals when a new snapshot is ready. + */ + private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable(); public static class Dependencies { public INetworkManagementService getNMS() { @@ -915,13 +922,21 @@ public class IpClient extends StateMachine { final ProvisioningConfiguration provisioningConfig = mConfiguration; final ApfCapabilities apfCapabilities = (provisioningConfig != null) ? provisioningConfig.mApfCapabilities : null; - final byte[] apfDataSnapshot = mApfDataSnapshot; IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); pw.println(mTag + " APF dump:"); pw.increaseIndent(); if (apfFilter != null) { + if (apfCapabilities.hasDataAccess()) { + // Request a new snapshot, then wait for it. + mApfDataSnapshotComplete.close(); + mCallback.startReadPacketFilter(); + if (!mApfDataSnapshotComplete.block(1000)) { + pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT"); + } + } apfFilter.dump(pw); + } else { pw.print("No active ApfFilter; "); if (provisioningConfig == null) { @@ -933,15 +948,6 @@ public class IpClient extends StateMachine { } } pw.decreaseIndent(); - pw.println(mTag + " latest APF data snapshot: "); - pw.increaseIndent(); - if (apfDataSnapshot != null) { - pw.println(HexDump.dumpHexString(apfDataSnapshot)); - } else { - pw.println("No last snapshot."); - } - pw.decreaseIndent(); - pw.println(); pw.println(mTag + " current ProvisioningConfiguration:"); pw.increaseIndent(); @@ -1738,7 +1744,10 @@ public class IpClient extends StateMachine { } case EVENT_READ_PACKET_FILTER_COMPLETE: { - mApfDataSnapshot = (byte[]) msg.obj; + if (mApfFilter != null) { + mApfFilter.setDataSnapshot((byte[]) msg.obj); + } + mApfDataSnapshotComplete.open(); break; }