diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java index 69013c0de637b..91bd023cd56cb 100644 --- a/core/java/android/net/metrics/RaEvent.java +++ b/core/java/android/net/metrics/RaEvent.java @@ -27,6 +27,9 @@ import android.os.Parcelable; @SystemApi public final class RaEvent implements Parcelable { + /** {@hide} */ + public static final long NO_LIFETIME = -1L; + // 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; @@ -92,4 +95,60 @@ public final class RaEvent implements Parcelable { return new RaEvent[size]; } }; + + /** {@hide} */ + public static class Builder { + + long routerLifetime = NO_LIFETIME; + long prefixValidLifetime = NO_LIFETIME; + long prefixPreferredLifetime = NO_LIFETIME; + long routeInfoLifetime = NO_LIFETIME; + long rdnssLifetime = NO_LIFETIME; + long dnsslLifetime = NO_LIFETIME; + + public Builder() { + } + + public RaEvent build() { + return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime, + routeInfoLifetime, rdnssLifetime, dnsslLifetime); + } + + public Builder updateRouterLifetime(long lifetime) { + routerLifetime = updateLifetime(routerLifetime, lifetime); + return this; + } + + public Builder updatePrefixValidLifetime(long lifetime) { + prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime); + return this; + } + + public Builder updatePrefixPreferredLifetime(long lifetime) { + prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime); + return this; + } + + public Builder updateRouteInfoLifetime(long lifetime) { + routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime); + return this; + } + + public Builder updateRdnssLifetime(long lifetime) { + rdnssLifetime = updateLifetime(rdnssLifetime, lifetime); + return this; + } + + public Builder updateDnsslLifetime(long lifetime) { + dnsslLifetime = updateLifetime(dnsslLifetime, lifetime); + return this; + } + + private long updateLifetime(long currentLifetime, long newLifetime) { + if (currentLifetime == RaEvent.NO_LIFETIME) { + return newLifetime; + } + return Math.min(currentLifetime, newLifetime); + } + } } diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 0ef9d7abaa38a..57f784a814ccf 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -213,7 +213,7 @@ public class ApfFilter { private final ApfCapabilities mApfCapabilities; private final IpManager.Callback mIpManagerCallback; private final NetworkInterface mNetworkInterface; - private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); + private final IpConnectivityLog mMetricsLog; @VisibleForTesting byte[] mHardwareAddress; @VisibleForTesting @@ -228,11 +228,12 @@ public class ApfFilter { @VisibleForTesting ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface, - IpManager.Callback ipManagerCallback, boolean multicastFilter) { + IpManager.Callback ipManagerCallback, boolean multicastFilter, IpConnectivityLog log) { mApfCapabilities = apfCapabilities; mIpManagerCallback = ipManagerCallback; mNetworkInterface = networkInterface; mMulticastFilter = multicastFilter; + mMetricsLog = log; maybeStartFilter(); } @@ -371,6 +372,14 @@ public class ApfFilter { return i & 0xffffffffL; } + private long getUint16(ByteBuffer buffer, int position) { + return uint16(buffer.getShort(position)); + } + + private long getUint32(ByteBuffer buffer, int position) { + return uint32(buffer.getInt(position)); + } + private void prefixOptionToString(StringBuffer sb, int offset) { String prefix = IPv6AddresstoString(offset + 16); int length = uint8(mPacket.get(offset + 2)); @@ -417,7 +426,7 @@ public class ApfFilter { * @param lifetimeOffset offset from mPacket.position() to the next lifetime data. * @param lifetimeLength length of the next lifetime data. * @return offset within packet of where the next binary range of data not including - * a lifetime. This can be passed into the next invocation of this function + * a lifetime. This can be passed into the next invocation of this function * via {@code lastNonLifetimeStart}. */ private int addNonLifetime(int lastNonLifetimeStart, int lifetimeOffset, @@ -438,9 +447,9 @@ public class ApfFilter { // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with // specifications. Ra(byte[] packet, int length) { - mPacket = ByteBuffer.allocate(length).put(ByteBuffer.wrap(packet, 0, length)); - mPacket.clear(); + mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length)); mLastSeen = curTime(); + RaEvent.Builder builder = new RaEvent.Builder(); // Ignore the checksum. int lastNonLifetimeStart = addNonLifetime(0, @@ -451,14 +460,7 @@ public class ApfFilter { lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart, 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; + builder.updateRouterLifetime(getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)); // Ensures that the RA is not truncated. mPacket.position(ICMP6_RA_OPTION_OFFSET); @@ -466,39 +468,42 @@ public class ApfFilter { final int position = mPacket.position(); final int optionType = uint8(mPacket.get(position)); final int optionLength = uint8(mPacket.get(position + 1)) * 8; + long lifetime; switch (optionType) { case ICMP6_PREFIX_OPTION_TYPE: // Parse valid lifetime lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart, ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET, ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN); + lifetime = getUint32(mPacket, + position + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET); + builder.updatePrefixValidLifetime(lifetime); // Parse preferred lifetime lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart, ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET, ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN); + lifetime = getUint32(mPacket, + position + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET); + builder.updatePrefixPreferredLifetime(lifetime); 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, and - // are processed with the same specialized addNonLifetime4B: + // are processed with the same specialized addNonLifetimeU32: case ICMP6_RDNSS_OPTION_TYPE: mRdnssOptionOffsets.add(position); lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart); - rdnssLifetime = - uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position)); + lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET); + builder.updateRdnssLifetime(lifetime); break; case ICMP6_ROUTE_INFO_OPTION_TYPE: lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart); - routeInfoLifetime = - uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position)); + lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET); + builder.updateRouteInfoLifetime(lifetime); break; case ICMP6_DNSSL_OPTION_TYPE: lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart); - dnsslLifetime = - uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position)); + lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET); + builder.updateDnsslLifetime(lifetime); break; default: // RFC4861 section 4.2 dictates we ignore unknown options for fowards @@ -514,9 +519,7 @@ public class ApfFilter { // 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)); + mMetricsLog.log(builder.build()); } // Ignoring lifetimes (which may change) does {@code packet} match this RA? @@ -1000,7 +1003,8 @@ public class ApfFilter { Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); return null; } - return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback, multicastFilter); + return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback, + multicastFilter, new IpConnectivityLog()); } public synchronized void shutdown() { diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java index af78839c82d6f..815133ad34571 100644 --- a/services/tests/servicestests/src/android/net/apf/ApfTest.java +++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java @@ -26,14 +26,23 @@ 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.IpConnectivityLog; +import android.net.metrics.RaEvent; import android.net.LinkAddress; import android.net.LinkProperties; import android.os.ConditionVariable; +import android.os.Parcelable; import android.system.ErrnoException; import android.system.Os; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; @@ -43,6 +52,7 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.nio.ByteBuffer; +import java.util.List; import libcore.io.IoUtils; import libcore.io.Streams; @@ -56,9 +66,12 @@ import libcore.io.Streams; public class ApfTest extends AndroidTestCase { private static final int TIMEOUT_MS = 500; + @Mock IpConnectivityLog mLog; + @Override public void setUp() throws Exception { super.setUp(); + MockitoAnnotations.initMocks(this); // Load up native shared library containing APF interpreter exposed via JNI. System.loadLibrary("servicestestsjni"); } @@ -70,6 +83,9 @@ public class ApfTest extends AndroidTestCase { // least the minimum packet size. private final static int MIN_PKT_SIZE = 15; + private final static boolean DROP_MULTICAST = true; + private final static boolean ALLOW_MULTICAST = false; + private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) { assertEquals(expected, apfSimulate(program, packet, filterAge)); } @@ -562,10 +578,10 @@ public class ApfTest extends AndroidTestCase { public final static byte[] MOCK_MAC_ADDR = new byte[]{1,2,3,4,5,6}; private FileDescriptor mWriteSocket; - public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter) throws - Exception { + public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter, + IpConnectivityLog log) throws Exception { super(new ApfCapabilities(2, 1000, ARPHRD_ETHER), NetworkInterface.getByName("lo"), - ipManagerCallback, multicastFilter); + ipManagerCallback, multicastFilter, log); } // Pretend an RA packet has been received and show it to ApfFilter. @@ -667,7 +683,7 @@ public class ApfTest extends AndroidTestCase { @LargeTest public void testApfFilterIPv4() throws Exception { MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); - ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, true /* multicastFilter */); + ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog); byte[] program = ipManagerCallback.getApfProgram(); // Verify empty packet of 100 zero bytes is passed @@ -699,7 +715,7 @@ public class ApfTest extends AndroidTestCase { @LargeTest public void testApfFilterIPv6() throws Exception { MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); - ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */); + ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog); byte[] program = ipManagerCallback.getApfProgram(); // Verify empty IPv6 packet is passed @@ -726,7 +742,7 @@ public class ApfTest extends AndroidTestCase { @LargeTest public void testApfFilterMulticast() throws Exception { MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); - ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */); + ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog); byte[] program = ipManagerCallback.getApfProgram(); // Construct IPv4 and IPv6 multicast packets. @@ -772,7 +788,7 @@ public class ApfTest extends AndroidTestCase { // Verify it can be initialized to on ipManagerCallback.resetApfProgramWait(); apfFilter.shutdown(); - apfFilter = new TestApfFilter(ipManagerCallback, true /* multicastFilter */); + apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog); program = ipManagerCallback.getApfProgram(); assertDrop(program, bcastv4packet.array(), 0); assertDrop(program, mcastv4packet.array(), 0); @@ -804,7 +820,7 @@ public class ApfTest extends AndroidTestCase { @LargeTest public void testApfFilterArp() throws Exception { MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); - ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */); + ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog); byte[] program = ipManagerCallback.getApfProgram(); // Verify initially ARP filter is off @@ -867,6 +883,35 @@ public class ApfTest extends AndroidTestCase { verifyRaLifetime(ipManagerCallback, packet, lifetime); } + private void verifyRaEvent(RaEvent expected) { + ArgumentCaptor captor = ArgumentCaptor.forClass(Parcelable.class); + verify(mLog, atLeastOnce()).log(captor.capture()); + RaEvent got = lastRaEvent(captor.getAllValues()); + if (!raEventEquals(expected, got)) { + assertEquals(expected, got); // fail for printing an assertion error message. + } + } + + private RaEvent lastRaEvent(List events) { + RaEvent got = null; + for (Parcelable ev : events) { + if (ev instanceof RaEvent) { + got = (RaEvent) ev; + } + } + return got; + } + + private boolean raEventEquals(RaEvent ev1, RaEvent ev2) { + return (ev1 != null) && (ev2 != null) + && (ev1.routerLifetime == ev2.routerLifetime) + && (ev1.prefixValidLifetime == ev2.prefixValidLifetime) + && (ev1.prefixPreferredLifetime == ev2.prefixPreferredLifetime) + && (ev1.routeInfoLifetime == ev2.routeInfoLifetime) + && (ev1.rdnssLifetime == ev2.rdnssLifetime) + && (ev1.dnsslLifetime == ev2.dnsslLifetime); + } + private void assertInvalidRa(TestApfFilter apfFilter, MockIpManagerCallback ipManagerCallback, ByteBuffer packet) throws IOException, ErrnoException { ipManagerCallback.resetApfProgramWait(); @@ -877,7 +922,7 @@ public class ApfTest extends AndroidTestCase { @LargeTest public void testApfFilterRa() throws Exception { MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); - TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, true /* multicastFilter */); + TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog); byte[] program = ipManagerCallback.getApfProgram(); // Verify RA is passed the first time @@ -891,6 +936,7 @@ public class ApfTest extends AndroidTestCase { assertPass(program, basePacket.array(), 0); testRaLifetime(apfFilter, ipManagerCallback, basePacket, 1000); + verifyRaEvent(new RaEvent(1000, -1, -1, -1, -1, -1)); // Ensure zero-length options cause the packet to be silently skipped. // Do this before we test other packets. http://b/29586253 @@ -916,6 +962,7 @@ public class ApfTest extends AndroidTestCase { prefixOptionPacket.putInt( ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET, 200); testRaLifetime(apfFilter, ipManagerCallback, prefixOptionPacket, 100); + verifyRaEvent(new RaEvent(1000, 200, 100, -1, -1, -1)); ByteBuffer rdnssOptionPacket = ByteBuffer.wrap( new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]); @@ -926,6 +973,7 @@ public class ApfTest extends AndroidTestCase { rdnssOptionPacket.putInt( ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 300); testRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, 300); + verifyRaEvent(new RaEvent(1000, -1, -1, -1, 300, -1)); ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap( new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]); @@ -936,6 +984,7 @@ public class ApfTest extends AndroidTestCase { routeInfoOptionPacket.putInt( ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 400); testRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, 400); + verifyRaEvent(new RaEvent(1000, -1, -1, 400, -1, -1)); ByteBuffer dnsslOptionPacket = ByteBuffer.wrap( new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]); @@ -948,6 +997,7 @@ public class ApfTest extends AndroidTestCase { // Note that lifetime of 2000 will be ignored in favor of shorter // route lifetime of 1000. testRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, 1000); + verifyRaEvent(new RaEvent(1000, -1, -1, -1, -1, 2000)); // Verify that current program filters all five RAs: verifyRaLifetime(ipManagerCallback, basePacket, 1000);