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 {
|
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 {
|
public final class DefaultNetworkEvent implements android.os.Parcelable {
|
||||||
method public int describeContents();
|
method public int describeContents();
|
||||||
method public static void logEvent(int, int[], int, boolean, boolean);
|
method public static void logEvent(int, int[], int, boolean, boolean);
|
||||||
@@ -26126,6 +26153,18 @@ package android.net.metrics {
|
|||||||
field public final int netId;
|
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 {
|
public final class ValidationProbeEvent implements android.os.Parcelable {
|
||||||
method public int describeContents();
|
method public int describeContents();
|
||||||
method public static void logEvent(int, long, int, int);
|
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
|
@SystemApi
|
||||||
public final class IpManagerEvent implements Parcelable {
|
public final class IpManagerEvent implements Parcelable {
|
||||||
|
|
||||||
|
// TODO: use @IntDef
|
||||||
public static final int PROVISIONING_OK = 1;
|
public static final int PROVISIONING_OK = 1;
|
||||||
public static final int PROVISIONING_FAIL = 2;
|
public static final int PROVISIONING_FAIL = 2;
|
||||||
public static final int COMPLETE_LIFECYCLE = 3;
|
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
|
@SystemApi
|
||||||
public final class ValidationProbeEvent implements Parcelable {
|
public final class ValidationProbeEvent implements Parcelable {
|
||||||
|
|
||||||
|
// TODO: use @IntDef
|
||||||
public static final int PROBE_DNS = 0;
|
public static final int PROBE_DNS = 0;
|
||||||
public static final int PROBE_HTTP = 1;
|
public static final int PROBE_HTTP = 1;
|
||||||
public static final int PROBE_HTTPS = 2;
|
public static final int PROBE_HTTPS = 2;
|
||||||
|
|||||||
@@ -18,15 +18,21 @@ package android.net.apf;
|
|||||||
|
|
||||||
import static android.system.OsConstants.*;
|
import static android.system.OsConstants.*;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.net.LinkProperties;
|
import android.net.LinkProperties;
|
||||||
import android.net.NetworkUtils;
|
import android.net.NetworkUtils;
|
||||||
import android.net.apf.ApfGenerator;
|
import android.net.apf.ApfGenerator;
|
||||||
import android.net.apf.ApfGenerator.IllegalInstructionException;
|
import android.net.apf.ApfGenerator.IllegalInstructionException;
|
||||||
import android.net.apf.ApfGenerator.Register;
|
import android.net.apf.ApfGenerator.Register;
|
||||||
import android.net.ip.IpManager;
|
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.ErrnoException;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.system.PacketSocketAddress;
|
import android.system.PacketSocketAddress;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
@@ -69,6 +75,17 @@ import libcore.io.IoBridge;
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class ApfFilter {
|
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.
|
// Thread to listen for RAs.
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
class ReceiveThread extends Thread {
|
class ReceiveThread extends Thread {
|
||||||
@@ -76,6 +93,16 @@ public class ApfFilter {
|
|||||||
private final FileDescriptor mSocket;
|
private final FileDescriptor mSocket;
|
||||||
private volatile boolean mStopped;
|
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) {
|
public ReceiveThread(FileDescriptor socket) {
|
||||||
mSocket = socket;
|
mSocket = socket;
|
||||||
}
|
}
|
||||||
@@ -94,13 +121,46 @@ public class ApfFilter {
|
|||||||
while (!mStopped) {
|
while (!mStopped) {
|
||||||
try {
|
try {
|
||||||
int length = Os.read(mSocket, mPacket, 0, mPacket.length);
|
int length = Os.read(mSocket, mPacket, 0, mPacket.length);
|
||||||
processRa(mPacket, length);
|
updateStats(processRa(mPacket, length));
|
||||||
} catch (IOException|ErrnoException e) {
|
} catch (IOException|ErrnoException e) {
|
||||||
if (!mStopped) {
|
if (!mStopped) {
|
||||||
Log.e(TAG, "Read error", e);
|
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
|
// 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 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[]{
|
private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{
|
||||||
0, 1, // Hardware type: Ethernet (1)
|
0, 1, // Hardware type: Ethernet (1)
|
||||||
8, 0, // Protocol type: IP (0x0800)
|
8, 0, // Protocol type: IP (0x0800)
|
||||||
@@ -148,11 +208,12 @@ public class ApfFilter {
|
|||||||
4, // Protocol size: 4
|
4, // Protocol size: 4
|
||||||
0, 1 // Opcode: request (1)
|
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 ApfCapabilities mApfCapabilities;
|
||||||
private final IpManager.Callback mIpManagerCallback;
|
private final IpManager.Callback mIpManagerCallback;
|
||||||
private final NetworkInterface mNetworkInterface;
|
private final NetworkInterface mNetworkInterface;
|
||||||
|
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
byte[] mHardwareAddress;
|
byte[] mHardwareAddress;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -212,8 +273,9 @@ public class ApfFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns seconds since Unix Epoch.
|
// Returns seconds since Unix Epoch.
|
||||||
|
// TODO: use SystemClock.elapsedRealtime() instead
|
||||||
private static long curTime() {
|
private static long curTime() {
|
||||||
return System.currentTimeMillis() / 1000L;
|
return System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A class to hold information about an RA.
|
// 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.
|
// 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) {
|
private int uint8(byte b) {
|
||||||
return b & 0xff;
|
return b & 0xff;
|
||||||
}
|
}
|
||||||
@@ -305,8 +367,8 @@ public class ApfFilter {
|
|||||||
return s & 0xffff;
|
return s & 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long uint32(int s) {
|
private long uint32(int i) {
|
||||||
return s & 0xffffffff;
|
return i & 0xffffffffL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prefixOptionToString(StringBuffer sb, int offset) {
|
private void prefixOptionToString(StringBuffer sb, int offset) {
|
||||||
@@ -366,6 +428,11 @@ public class ApfFilter {
|
|||||||
return lifetimeOffset + lifetimeLength;
|
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
|
// Note that this parses RA and may throw IllegalArgumentException (from
|
||||||
// Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
|
// Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
|
||||||
// (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
|
// (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_OFFSET,
|
||||||
ICMP6_RA_ROUTER_LIFETIME_LEN);
|
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.
|
// Ensures that the RA is not truncated.
|
||||||
mPacket.position(ICMP6_RA_OPTION_OFFSET);
|
mPacket.position(ICMP6_RA_OPTION_OFFSET);
|
||||||
while (mPacket.hasRemaining()) {
|
while (mPacket.hasRemaining()) {
|
||||||
int optionType = ((int)mPacket.get(mPacket.position())) & 0xff;
|
final int position = mPacket.position();
|
||||||
int optionLength = (((int)mPacket.get(mPacket.position() + 1)) & 0xff) * 8;
|
final int optionType = uint8(mPacket.get(position));
|
||||||
|
final int optionLength = uint8(mPacket.get(position + 1)) * 8;
|
||||||
switch (optionType) {
|
switch (optionType) {
|
||||||
case ICMP6_PREFIX_OPTION_TYPE:
|
case ICMP6_PREFIX_OPTION_TYPE:
|
||||||
// Parse valid lifetime
|
// Parse valid lifetime
|
||||||
@@ -400,19 +476,29 @@ public class ApfFilter {
|
|||||||
lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
|
lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
|
||||||
ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
|
ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
|
||||||
ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN);
|
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;
|
break;
|
||||||
// These three options have the same lifetime offset and size, so process
|
// These three options have the same lifetime offset and size, and
|
||||||
// together:
|
// are processed with the same specialized addNonLifetime4B:
|
||||||
case ICMP6_RDNSS_OPTION_TYPE:
|
case ICMP6_RDNSS_OPTION_TYPE:
|
||||||
mRdnssOptionOffsets.add(mPacket.position());
|
mRdnssOptionOffsets.add(position);
|
||||||
// Fall through.
|
lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
|
||||||
|
rdnssLifetime =
|
||||||
|
uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position));
|
||||||
|
break;
|
||||||
case ICMP6_ROUTE_INFO_OPTION_TYPE:
|
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:
|
case ICMP6_DNSSL_OPTION_TYPE:
|
||||||
// Parse lifetime
|
lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
|
||||||
lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
|
dnsslLifetime =
|
||||||
ICMP6_4_BYTE_LIFETIME_OFFSET,
|
uint32(mPacket.getInt(ICMP6_4_BYTE_LIFETIME_OFFSET + position));
|
||||||
ICMP6_4_BYTE_LIFETIME_LEN);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// RFC4861 section 4.2 dictates we ignore unknown options for fowards
|
// RFC4861 section 4.2 dictates we ignore unknown options for fowards
|
||||||
@@ -423,11 +509,14 @@ public class ApfFilter {
|
|||||||
throw new IllegalArgumentException(String.format(
|
throw new IllegalArgumentException(String.format(
|
||||||
"Invalid option length opt=%d len=%d", optionType, optionLength));
|
"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.
|
// Mark non-lifetime bytes since last lifetime.
|
||||||
addNonLifetime(lastNonLifetimeStart, 0, 0);
|
addNonLifetime(lastNonLifetimeStart, 0, 0);
|
||||||
mMinLifetime = minLifetime(packet, length);
|
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?
|
// Ignoring lifetimes (which may change) does {@code packet} match this RA?
|
||||||
@@ -456,16 +545,19 @@ public class ApfFilter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
|
final int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
|
||||||
long val;
|
final long optionLifetime;
|
||||||
switch (lifetimeLength) {
|
switch (lifetimeLength) {
|
||||||
case 2: val = byteBuffer.getShort(offset); break;
|
case 2:
|
||||||
case 4: val = byteBuffer.getInt(offset); break;
|
optionLifetime = uint16(byteBuffer.getShort(offset));
|
||||||
default: throw new IllegalStateException("bogus lifetime size " + length);
|
break;
|
||||||
|
case 4:
|
||||||
|
optionLifetime = uint32(byteBuffer.getInt(offset));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("bogus lifetime size " + lifetimeLength);
|
||||||
}
|
}
|
||||||
// Mask to size, converting signed to unsigned
|
minLifetime = Math.min(minLifetime, optionLifetime);
|
||||||
val &= (1L << (lifetimeLength * 8)) - 1;
|
|
||||||
minLifetime = Math.min(minLifetime, val);
|
|
||||||
}
|
}
|
||||||
return minLifetime;
|
return minLifetime;
|
||||||
}
|
}
|
||||||
@@ -760,16 +852,19 @@ public class ApfFilter {
|
|||||||
return gen;
|
return gen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate and install a new filter program.
|
||||||
|
*/
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void installNewProgramLocked() {
|
void installNewProgramLocked() {
|
||||||
purgeExpiredRasLocked();
|
purgeExpiredRasLocked();
|
||||||
|
ArrayList<Ra> rasToFilter = new ArrayList<>();
|
||||||
final byte[] program;
|
final byte[] program;
|
||||||
long programMinLifetime = Long.MAX_VALUE;
|
long programMinLifetime = Long.MAX_VALUE;
|
||||||
try {
|
try {
|
||||||
// Step 1: Determine how many RA filters we can fit in the program.
|
// Step 1: Determine how many RA filters we can fit in the program.
|
||||||
ApfGenerator gen = beginProgramLocked();
|
ApfGenerator gen = beginProgramLocked();
|
||||||
ArrayList<Ra> rasToFilter = new ArrayList<Ra>();
|
|
||||||
for (Ra ra : mRas) {
|
for (Ra ra : mRas) {
|
||||||
ra.generateFilterLocked(gen);
|
ra.generateFilterLocked(gen);
|
||||||
// Stop if we get too big.
|
// Stop if we get too big.
|
||||||
@@ -797,17 +892,17 @@ public class ApfFilter {
|
|||||||
hexDump("Installing filter: ", program, program.length);
|
hexDump("Installing filter: ", program, program.length);
|
||||||
}
|
}
|
||||||
mIpManagerCallback.installPacketFilter(program);
|
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")
|
* Returns {@code true} if a new program should be installed because the current one dies soon.
|
||||||
private void maybeInstallNewProgramLocked() {
|
*/
|
||||||
if (mRas.size() == 0) return;
|
private boolean shouldInstallnewProgram() {
|
||||||
// If the current program doesn't expire for a while, don't bother updating.
|
|
||||||
long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
|
long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
|
||||||
if (expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING) {
|
return expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
|
||||||
installNewProgramLocked();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hexDump(String msg, byte[] packet, int length) {
|
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);
|
if (VDBG) hexDump("Read packet = ", packet, length);
|
||||||
|
|
||||||
// Have we seen this RA before?
|
// Have we seen this RA before?
|
||||||
@@ -848,25 +948,34 @@ public class ApfFilter {
|
|||||||
// Swap to front of array.
|
// Swap to front of array.
|
||||||
mRas.add(0, mRas.remove(i));
|
mRas.add(0, mRas.remove(i));
|
||||||
|
|
||||||
maybeInstallNewProgramLocked();
|
// If the current program doesn't expire for a while, don't update.
|
||||||
return;
|
if (shouldInstallnewProgram()) {
|
||||||
|
installNewProgramLocked();
|
||||||
|
return ProcessRaResult.UPDATE_EXPIRY;
|
||||||
|
}
|
||||||
|
return ProcessRaResult.MATCH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
purgeExpiredRasLocked();
|
purgeExpiredRasLocked();
|
||||||
// TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
|
// 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;
|
final Ra ra;
|
||||||
try {
|
try {
|
||||||
ra = new Ra(packet, length);
|
ra = new Ra(packet, length);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error parsing RA: " + e);
|
Log.e(TAG, "Error parsing RA: " + e);
|
||||||
return;
|
return ProcessRaResult.PARSE_ERROR;
|
||||||
}
|
}
|
||||||
// Ignore 0 lifetime RAs.
|
// Ignore 0 lifetime RAs.
|
||||||
if (ra.isExpired()) return;
|
if (ra.isExpired()) {
|
||||||
|
return ProcessRaResult.ZERO_LIFETIME;
|
||||||
|
}
|
||||||
log("Adding " + ra);
|
log("Adding " + ra);
|
||||||
mRas.add(ra);
|
mRas.add(ra);
|
||||||
installNewProgramLocked();
|
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_PORT = 68;
|
||||||
private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
|
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[]{
|
private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{
|
||||||
0, 1, // Hardware type: Ethernet (1)
|
0, 1, // Hardware type: Ethernet (1)
|
||||||
8, 0, // Protocol type: IP (0x0800)
|
8, 0, // Protocol type: IP (0x0800)
|
||||||
@@ -660,9 +660,9 @@ public class ApfTest extends AndroidTestCase {
|
|||||||
4, // Protocol size: 4
|
4, // Protocol size: 4
|
||||||
0, 1 // Opcode: request (1)
|
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
|
@LargeTest
|
||||||
public void testApfFilterIPv4() throws Exception {
|
public void testApfFilterIPv4() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user