Merge changes Ia28652e0,Id2eaafdc,I9c4c8286 into nyc-mr1-dev
* changes: Record events for RA option lifetimes Log RA listening statistics Log events at APF program generation
This commit is contained in:
committed by
Android (Google) Code Review
commit
f5e3481951
@@ -26018,6 +26018,33 @@ 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<android.net.metrics.ApfProgramEvent> 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 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<android.net.metrics.ApfStats> 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);
|
||||
@@ -26126,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<android.net.metrics.RaEvent> 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);
|
||||
|
||||
137
core/java/android/net/metrics/ApfProgramEvent.java
Normal file
137
core/java/android/net/metrics/ApfProgramEvent.java
Normal file
@@ -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<ApfProgramEvent> CREATOR
|
||||
= new Parcelable.Creator<ApfProgramEvent>() {
|
||||
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<String> constants =
|
||||
MessageUtils.findMessageNames(
|
||||
new Class[]{ApfProgramEvent.class}, new String[]{"FLAG_"});
|
||||
}
|
||||
}
|
||||
103
core/java/android/net/metrics/ApfStats.java
Normal file
103
core/java/android/net/metrics/ApfStats.java
Normal file
@@ -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<ApfStats> CREATOR = new Parcelable.Creator<ApfStats>() {
|
||||
public ApfStats createFromParcel(Parcel in) {
|
||||
return new ApfStats(in);
|
||||
}
|
||||
|
||||
public ApfStats[] newArray(int size) {
|
||||
return new ApfStats[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
95
core/java/android/net/metrics/RaEvent.java
Normal file
95
core/java/android/net/metrics/RaEvent.java
Normal file
@@ -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<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
|
||||
public RaEvent createFromParcel(Parcel in) {
|
||||
return new RaEvent(in);
|
||||
}
|
||||
|
||||
public RaEvent[] newArray(int size) {
|
||||
return new RaEvent[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -18,15 +18,21 @@ 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;
|
||||
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.net.metrics.RaEvent;
|
||||
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;
|
||||
|
||||
@@ -69,6 +75,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 {
|
||||
@@ -76,6 +93,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;
|
||||
}
|
||||
@@ -94,13 +121,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +200,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 +208,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
|
||||
@@ -212,8 +273,9 @@ public class ApfFilter {
|
||||
}
|
||||
|
||||
// Returns seconds since Unix Epoch.
|
||||
// TODO: use SystemClock.elapsedRealtime() instead
|
||||
private static long curTime() {
|
||||
return System.currentTimeMillis() / 1000L;
|
||||
return System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS;
|
||||
}
|
||||
|
||||
// A class to hold information about an RA.
|
||||
@@ -296,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;
|
||||
}
|
||||
@@ -305,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) {
|
||||
@@ -366,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
|
||||
@@ -385,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
|
||||
@@ -400,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
|
||||
@@ -423,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?
|
||||
@@ -456,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;
|
||||
}
|
||||
@@ -760,16 +852,19 @@ public class ApfFilter {
|
||||
return gen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and install a new filter program.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
@VisibleForTesting
|
||||
void installNewProgramLocked() {
|
||||
purgeExpiredRasLocked();
|
||||
ArrayList<Ra> 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<Ra> rasToFilter = new ArrayList<Ra>();
|
||||
for (Ra ra : mRas) {
|
||||
ra.generateFilterLocked(gen);
|
||||
// Stop if we get too big.
|
||||
@@ -797,17 +892,17 @@ 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.
|
||||
@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) {
|
||||
@@ -826,7 +921,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?
|
||||
@@ -848,25 +948,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user