Snap for 12792407 from 72262523b931e2434d691ced3d93497fd6fb5106 to mainline-appsearch-release Change-Id: I8da83f57d1b37745a1f117ad7238df7e44c1272c
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java index c88587b..c9f8aba 100644 --- a/src/android/net/apf/AndroidPacketFilter.java +++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -120,4 +120,9 @@ default boolean shouldEnableMdnsOffload() { return false; } + + /** + * Update the multicast addresses that will be used by APF. + */ + default void updateIPv4MulticastAddrs() {} }
diff --git a/src/android/net/apf/ApfConstants.java b/src/android/net/apf/ApfConstants.java index a23e970..5aa8a6a 100644 --- a/src/android/net/apf/ApfConstants.java +++ b/src/android/net/apf/ApfConstants.java
@@ -15,6 +15,8 @@ */ package android.net.apf; +import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; + /** * The class which declares constants used in ApfFilter and unit tests. */ @@ -60,6 +62,10 @@ public static final byte[] IPV6_SOLICITED_NODES_PREFIX = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, (byte) 0xff}; + public static final int ICMP4_TYPE_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN; + public static final int ICMP4_CHECKSUM_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 2; + public static final int ICMP4_CONTENT_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 4; + public static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; public static final int ICMP6_CODE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 1; public static final int ICMP6_CHECKSUM_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java index 5bd9877..c2c47fa 100644 --- a/src/android/net/apf/ApfCounterTracker.java +++ b/src/android/net/apf/ApfCounterTracker.java
@@ -26,7 +26,7 @@ import java.util.Map; /** - * Common counter class for {@code ApfFilter} and {@code LegacyApfFilter}. + * Counter class for {@code ApfFilter}. * * @hide */
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 08a370d..68d2136 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -350,6 +350,10 @@ // Our tentative IPv6 addresses private Set<Inet6Address> mIPv6TentativeAddresses = new ArraySet<>(); + // Our joined IPv4 multicast addresses + @VisibleForTesting + public Set<Inet4Address> mIPv4MulticastAddresses = new ArraySet<>(); + // Whether CLAT is enabled. private boolean mHasClat; @@ -603,6 +607,14 @@ public int getNdTrafficClass(@NonNull String ifname) { return ProcfsParsingUtils.getNdTrafficClass(ifname); } + + /** + * Loads the existing IPv4 multicast addresses from the file + * `/proc/net/igmp`. + */ + public List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) { + return ProcfsParsingUtils.getIPv4MulticastAddresses(ifname); + } } @Override @@ -1321,7 +1333,7 @@ NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { mPacket = new NattKeepaliveResponseData(sentKeepalivePacket); - mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress); + mSrcDstAddr = CollectionUtils.concatArrays(mPacket.srcAddress, mPacket.dstAddress); mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort); } @@ -1445,7 +1457,8 @@ this(new TcpKeepaliveAckData(sentKeepalivePacket)); } TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) { - super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + super(packet, CollectionUtils.concatArrays(packet.srcAddress, + packet.dstAddress) /* srcDstAddr */); } @Override @@ -1487,7 +1500,8 @@ this(new TcpKeepaliveAckData(sentKeepalivePacket)); } TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) { - super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + super(packet, CollectionUtils.concatArrays(packet.srcAddress, + packet.dstAddress) /* srcDstAddr */); } @Override @@ -2681,6 +2695,17 @@ } @Override + public void updateIPv4MulticastAddrs() { + final Set<Inet4Address> mcastAddrs = + new ArraySet<>(mDependencies.getIPv4MulticastAddresses(mInterfaceParams.name)); + + if (!mIPv4MulticastAddresses.equals(mcastAddrs)) { + mIPv4MulticastAddresses = mcastAddrs; + installNewProgram(); + } + } + + @Override public boolean supportNdOffload() { return shouldUseApfV6Generator() && mShouldHandleNdOffload; } @@ -3010,20 +3035,6 @@ + (uint8(bytes[3])); } - private static byte[] concatArrays(final byte[]... arr) { - int size = 0; - for (byte[] a : arr) { - size += a.length; - } - final byte[] result = new byte[size]; - int offset = 0; - for (byte[] a : arr) { - System.arraycopy(a, 0, result, offset, a.length); - offset += a.length; - } - return result; - } - private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) { if (mNetworkQuirkMetrics == null) return; mNetworkQuirkMetrics.setEvent(event);
diff --git a/src/android/net/apf/ApfMdnsUtils.java b/src/android/net/apf/ApfMdnsUtils.java index 7666864..b2695c0 100644 --- a/src/android/net/apf/ApfMdnsUtils.java +++ b/src/android/net/apf/ApfMdnsUtils.java
@@ -27,6 +27,7 @@ import android.os.Build; import android.util.ArraySet; +import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.DnsUtils; import java.io.ByteArrayOutputStream; @@ -56,14 +57,6 @@ allMatchers.add(matcher); } - private static String[] prepend(String[] suffix, String... prefixes) { - String[] result = new String[prefixes.length + suffix.length]; - System.arraycopy(prefixes, 0, result, 0, prefixes.length); - System.arraycopy(suffix, 0, result, prefixes.length, suffix.length); - return result; - } - - /** * Extract the offload rules from the list of offloadServiceInfos. The rules are returned in * priority order (most important first). If there are too many rules, APF could decide only @@ -91,7 +84,8 @@ List<MdnsOffloadRule.Matcher> matcherGroup = new ArrayList<>(); final OffloadServiceInfo.Key key = info.getKey(); final String[] serviceTypeLabels = key.getServiceType().split("\\.", 0); - final String[] fullQualifiedName = prepend(serviceTypeLabels, key.getServiceName()); + final String[] fullQualifiedName = CollectionUtils.prependArray(String.class, + serviceTypeLabels, key.getServiceName()); final byte[] replyPayload = info.getOffloadPayload(); final byte[] encodedServiceType = encodeQname(serviceTypeLabels); // If (QTYPE == PTR) and (QNAME == mServiceName + mServiceType), then reply. @@ -106,7 +100,8 @@ boolean tooManySubtypes = subTypes.size() > MAX_SUPPORTED_SUBTYPES; if (tooManySubtypes) { // If (QTYPE == PTR) and (QNAME == wildcard + _sub + mServiceType), then fail open. - final String[] serviceTypeSuffix = prepend(serviceTypeLabels, "_sub"); + final String[] serviceTypeSuffix = CollectionUtils.prependArray(String.class, + serviceTypeLabels, "_sub"); final ByteArrayOutputStream buf = new ByteArrayOutputStream(); // byte = 0xff is used as a wildcard. buf.write(-1); @@ -117,7 +112,8 @@ } else { // If (QTYPE == PTR) and (QNAME == subType + _sub + mServiceType), then reply. for (String subType : subTypes) { - final String[] fullServiceType = prepend(serviceTypeLabels, subType, "_sub"); + final String[] fullServiceType = CollectionUtils.prependArray(String.class, + serviceTypeLabels, subType, "_sub"); final byte[] encodedFullServiceType = encodeQname(fullServiceType); // If (QTYPE == PTR) and (QNAME == subType + "_sub" + mServiceType), then reply. final MdnsOffloadRule.Matcher subtypePtrMatcher = new MdnsOffloadRule.Matcher(
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java deleted file mode 100644 index 2cd0eec..0000000 --- a/src/android/net/apf/LegacyApfFilter.java +++ /dev/null
@@ -1,2439 +0,0 @@ -/* - * 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.apf; - -import static android.net.apf.BaseApfGenerator.MemorySlot; -import static android.net.apf.BaseApfGenerator.Register.R0; -import static android.net.apf.BaseApfGenerator.Register.R1; -import static android.net.util.SocketUtils.makePacketSocketAddress; -import static android.system.OsConstants.AF_PACKET; -import static android.system.OsConstants.ETH_P_ARP; -import static android.system.OsConstants.ETH_P_IP; -import static android.system.OsConstants.ETH_P_IPV6; -import static android.system.OsConstants.IPPROTO_ICMPV6; -import static android.system.OsConstants.IPPROTO_TCP; -import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.SOCK_RAW; - -import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; -import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; - -import android.annotation.Nullable; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.NattKeepalivePacketDataParcelable; -import android.net.TcpKeepalivePacketDataParcelable; -import android.net.apf.ApfCounterTracker.Counter; -import android.net.apf.BaseApfGenerator.IllegalInstructionException; -import android.net.ip.IpClient.IpClientCallbacksWrapper; -import android.net.metrics.ApfProgramEvent; -import android.net.metrics.ApfStats; -import android.net.metrics.IpConnectivityLog; -import android.net.metrics.RaEvent; -import android.os.PowerManager; -import android.os.SystemClock; -import android.stats.connectivity.NetworkQuirkEvent; -import android.system.ErrnoException; -import android.system.Os; -import android.text.format.DateUtils; -import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.HexDump; -import com.android.internal.util.IndentingPrintWriter; -import com.android.net.module.util.CollectionUtils; -import com.android.net.module.util.ConnectivityUtils; -import com.android.net.module.util.InterfaceParams; -import com.android.net.module.util.SocketUtils; -import com.android.networkstack.metrics.ApfSessionInfoMetrics; -import com.android.networkstack.metrics.IpClientRaInfoMetrics; -import com.android.networkstack.metrics.NetworkQuirkMetrics; -import com.android.networkstack.util.NetworkStackUtils; - -import java.io.ByteArrayOutputStream; -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * For networks that support packet filtering via APF programs, {@code ApfFilter} - * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to - * filter out redundant duplicate ones. - * - * Threading model: - * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to - * know what RAs to filter for, thus generating APF programs is dependent on mRas. - * mRas can be accessed by multiple threads: - * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs. - * - callers of: - * - setMulticastFilter(), which can cause an APF program to be generated. - * - dump(), which dumps mRas among other things. - * - shutdown(), which clears mRas. - * So access to mRas is synchronized. - * - * @hide - */ -public class LegacyApfFilter implements AndroidPacketFilter { - - // 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 - } - - /** - * When APFv4 is supported, loads R1 with the offset of the specified counter. - */ - private void maybeSetupCounter(ApfV4Generator gen, Counter c) { - if (hasDataAccess(mApfVersionSupported)) { - gen.addLoadImmediate(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; - - /** A wrapper class of {@link SystemClock} to be mocked in unit tests. */ - public static class Clock { - /** - * @see SystemClock#elapsedRealtime - */ - public long elapsedRealtime() { - return SystemClock.elapsedRealtime(); - } - } - - // Thread to listen for RAs. - @VisibleForTesting - public class ReceiveThread extends Thread { - private final byte[] mPacket = new byte[1514]; - private final FileDescriptor mSocket; - private final long mStart = mClock.elapsedRealtime(); - - private int mReceivedRas = 0; - private int mMatchingRas = 0; - private int mDroppedRas = 0; - private int mParseErrors = 0; - private int mZeroLifetimeRas = 0; - private int mProgramUpdates = 0; - - private volatile boolean mStopped; - - public ReceiveThread(FileDescriptor socket) { - mSocket = socket; - } - - public void halt() { - mStopped = true; - // Interrupts the read() call the thread is blocked in. - SocketUtils.closeSocketQuietly(mSocket); - } - - @Override - public void run() { - log("begin monitoring"); - while (!mStopped) { - try { - int length = Os.read(mSocket, mPacket, 0, 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() { - final long nowMs = mClock.elapsedRealtime(); - synchronized (LegacyApfFilter.this) { - final ApfStats stats = new ApfStats.Builder() - .setReceivedRas(mReceivedRas) - .setMatchingRas(mMatchingRas) - .setDroppedRas(mDroppedRas) - .setParseErrors(mParseErrors) - .setZeroLifetimeRas(mZeroLifetimeRas) - .setProgramUpdates(mProgramUpdates) - .setDurationMs(nowMs - mStart) - .setMaxProgramSize(mMaximumApfProgramSize) - .setProgramUpdatesAll(mNumProgramUpdates) - .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast) - .build(); - mMetricsLog.log(stats); - logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS); - } - } - } - - private static final String TAG = "ApfFilter"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - private static final int ETH_HEADER_LEN = 14; - private static final int ETH_DEST_ADDR_OFFSET = 0; - private static final int ETH_ETHERTYPE_OFFSET = 12; - private static final int ETH_TYPE_MIN = 0x0600; - private static final int ETH_TYPE_MAX = 0xFFFF; - // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN. - private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; - private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6; - // Endianness is not an issue for this constant because the APF interpreter always operates in - // network byte order. - private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff; - private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; - private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; - private static final int IPV4_ANY_HOST_ADDRESS = 0; - private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255 - private static final int IPV4_HEADER_LEN = 20; // Without options - - // Traffic class and Flow label are not byte aligned. Luckily we - // don't care about either value so we'll consider bytes 1-3 of the - // IPv6 header as don't care. - private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1; - private static final int IPV6_FLOW_LABEL_LEN = 3; - private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6; - private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8; - private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24; - private static final int IPV6_HEADER_LEN = 40; - // The IPv6 all nodes address ff02::1 - private static final byte[] IPV6_ALL_NODES_ADDRESS = - { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; - - private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; - - private static final int IPPROTO_HOPOPTS = 0; - - // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE - private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2; - private static final int UDP_HEADER_LEN = 8; - - private static final int TCP_HEADER_SIZE_OFFSET = 12; - - private static final int DHCP_CLIENT_PORT = 68; - // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE - private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28; - - private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; - private static final byte[] ARP_IPV4_HEADER = { - 0, 1, // Hardware type: Ethernet (1) - 8, 0, // Protocol type: IP (0x0800) - 6, // Hardware size: 6 - 4, // Protocol size: 4 - }; - private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6; - // Opcode: ARP request (0x0001), ARP reply (0x0002) - private static final short ARP_OPCODE_REQUEST = 1; - private static final short ARP_OPCODE_REPLY = 2; - private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14; - private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24; - // Do not log ApfProgramEvents whose actual lifetimes was less than this. - private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2; - // Limit on the Black List size to cap on program usage for this - // TODO: Select a proper max length - private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; - - private static final byte[] ETH_MULTICAST_MDNS_V4_MAC_ADDRESS = - {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; - private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS = - {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; - private static final int MDNS_PORT = 5353; - private static final int DNS_HEADER_LEN = 12; - private static final int DNS_QDCOUNT_OFFSET = 4; - // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE, or the - // IPv6 header length. - private static final int MDNS_QDCOUNT_OFFSET = - ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_QDCOUNT_OFFSET; - private static final int MDNS_QNAME_OFFSET = - ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN; - - - public final int mApfVersionSupported; - public final int mMaximumApfProgramSize; - private final IpClientCallbacksWrapper mIpClientCallback; - private final InterfaceParams mInterfaceParams; - private final IpConnectivityLog mMetricsLog; - - @VisibleForTesting - public byte[] mHardwareAddress; - @VisibleForTesting - public ReceiveThread mReceiveThread; - @GuardedBy("this") - private long mUniqueCounter; - @GuardedBy("this") - private boolean mMulticastFilter; - @GuardedBy("this") - private boolean mInDozeMode; - private final boolean mDrop802_3Frames; - private final int[] mEthTypeBlackList; - - private final ApfCounterTracker mApfCounterTracker = new ApfCounterTracker(); - @GuardedBy("this") - private long mSessionStartMs = 0; - @GuardedBy("this") - private int mNumParseErrorRas = 0; - @GuardedBy("this") - private int mNumZeroLifetimeRas = 0; - @GuardedBy("this") - private int mLowestRouterLifetimeSeconds = Integer.MAX_VALUE; - @GuardedBy("this") - private long mLowestPioValidLifetimeSeconds = Long.MAX_VALUE; - @GuardedBy("this") - private long mLowestRioRouteLifetimeSeconds = Long.MAX_VALUE; - @GuardedBy("this") - private long mLowestRdnssLifetimeSeconds = Long.MAX_VALUE; - - // Ignore non-zero RDNSS lifetimes below this value. - private final int mMinRdnssLifetimeSec; - - // Minimum session time for metrics, duration less than this time will not be logged. - private final long mMinMetricsSessionDurationMs; - - private final Clock mClock; - private final NetworkQuirkMetrics mNetworkQuirkMetrics; - private final IpClientRaInfoMetrics mIpClientRaInfoMetrics; - private final ApfSessionInfoMetrics mApfSessionInfoMetrics; - - // Detects doze mode state transitions. - private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) { - PowerManager powerManager = - (PowerManager) context.getSystemService(Context.POWER_SERVICE); - final boolean deviceIdle = powerManager.isDeviceIdleMode(); - setDozeMode(deviceIdle); - } - } - }; - private final Context mContext; - - // Our IPv4 address, if we have just one, otherwise null. - @GuardedBy("this") - private byte[] mIPv4Address; - // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null. - @GuardedBy("this") - private int mIPv4PrefixLength; - - // mIsRunning is reflects the state of the LegacyApfFilter during integration tests. - // LegacyApfFilter can be paused using "adb shell cmd apf <iface> <cmd>" commands. A paused - // LegacyApfFilter will not install any new programs, but otherwise operates normally. - private volatile boolean mIsRunning = true; - - private final ApfFilter.Dependencies mDependencies; - - @VisibleForTesting - public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, - IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics) { - this(context, config, ifParams, ipClientCallback, log, networkQuirkMetrics, - new ApfFilter.Dependencies(context), new Clock()); - } - - @VisibleForTesting - public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, - IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics, - ApfFilter.Dependencies dependencies, Clock clock) { - mApfVersionSupported = config.apfVersionSupported; - mMaximumApfProgramSize = config.apfRamSize; - mIpClientCallback = ipClientCallback; - mInterfaceParams = ifParams; - mMulticastFilter = config.multicastFilter; - mDrop802_3Frames = config.ieee802_3Filter; - mMinRdnssLifetimeSec = config.minRdnssLifetimeSec; - mContext = context; - mClock = clock; - mDependencies = dependencies; - mNetworkQuirkMetrics = networkQuirkMetrics; - mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics(); - mApfSessionInfoMetrics = dependencies.getApfSessionInfoMetrics(); - mSessionStartMs = mClock.elapsedRealtime(); - mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs; - - if (hasDataAccess(mApfVersionSupported)) { - 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 = ApfV4Generator.PASS_LABEL; - mCountAndDropLabel = ApfV4Generator.DROP_LABEL; - } - - // Now fill the black list from the passed array - mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList); - - mMetricsLog = log; - - // TODO: ApfFilter should not generate programs until IpClient sends provisioning success. - maybeStartFilter(); - - // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter. - mContext.registerReceiver(mDeviceIdleReceiver, - new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); - - mDependencies.onApfFilterCreated(this); - // mReceiveThread is created in maybeStartFilter() and halted in shutdown(). - mDependencies.onThreadCreated(mReceiveThread); - } - - @Override - public synchronized String setDataSnapshot(byte[] data) { - mDataSnapshot = data; - if (mIsRunning) { - mApfCounterTracker.updateCountersFromData(data); - } - return mApfCounterTracker.getCounters().toString(); - } - - private void log(String s) { - Log.d(TAG, "(" + mInterfaceParams.name + "): " + s); - } - - @GuardedBy("this") - private long getUniqueNumberLocked() { - return mUniqueCounter++; - } - - private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) { - ArrayList<Integer> bl = new ArrayList<Integer>(); - - for (int p : ethTypeBlackList) { - // Check if the protocol is a valid ether type - if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) { - continue; - } - - // Check if the protocol is not repeated in the passed array - if (bl.contains(p)) { - continue; - } - - // Check if list reach its max size - if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) { - Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() + - ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols"); - break; - } - - // Now add the protocol to the list - bl.add(p); - } - - return bl.stream().mapToInt(Integer::intValue).toArray(); - } - - /** - * Attempt to start listening for RAs and, if RAs are received, generating and installing - * filters to ignore useless RAs. - */ - @VisibleForTesting - public void maybeStartFilter() { - FileDescriptor socket; - try { - mHardwareAddress = mInterfaceParams.macAddr.toByteArray(); - synchronized(this) { - // Clear the APF memory to reset all counters upon connecting to the first AP - // in an SSID. This is limited to APFv4 devices because this large write triggers - // a crash on some older devices (b/78905546). - if (mIsRunning && hasDataAccess(mApfVersionSupported)) { - byte[] zeroes = new byte[mMaximumApfProgramSize]; - if (!mIpClientCallback.installPacketFilter(zeroes)) { - sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); - } - } - - // Install basic filters - installNewProgramLocked(); - } - socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6); - SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index); - Os.bind(socket, addr); - NetworkStackUtils.attachRaFilter(socket); - } catch(SocketException|ErrnoException e) { - Log.e(TAG, "Error starting filter", e); - return; - } - mReceiveThread = new ReceiveThread(socket); - mReceiveThread.start(); - } - - // Returns seconds since device boot. - @VisibleForTesting - protected long currentTimeSeconds() { - return mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS; - } - - public static class InvalidRaException extends Exception { - public InvalidRaException(String m) { - super(m); - } - } - - /** - * Class to keep track of a section in a packet. - */ - private static class PacketSection { - public enum Type { - MATCH, // A field that should be matched (e.g., the router IP address). - IGNORE, // An ignored field such as the checksum of the flow label. Not matched. - LIFETIME, // A lifetime. Not matched, and generally counts toward minimum RA lifetime. - } - - /** The type of section. */ - public final Type type; - /** Offset into the packet at which this section begins. */ - public final int start; - /** Length of this section in bytes. */ - public final int length; - /** If this is a lifetime, the ICMP option that defined it. 0 for router lifetime. */ - public final int option; - /** If this is a lifetime, the lifetime value. */ - public final long lifetime; - - PacketSection(int start, int length, Type type, int option, long lifetime) { - this.start = start; - this.length = length; - this.type = type; - this.option = option; - this.lifetime = lifetime; - } - - public String toString() { - if (type == Type.LIFETIME) { - return String.format("%s: (%d, %d) %d %d", type, start, length, option, lifetime); - } else { - return String.format("%s: (%d, %d)", type, start, length); - } - } - } - - // A class to hold information about an RA. - @VisibleForTesting - public class Ra { - // From RFC4861: - private static final int ICMP6_RA_HEADER_LEN = 16; - private static final int ICMP6_RA_CHECKSUM_OFFSET = - ETH_HEADER_LEN + IPV6_HEADER_LEN + 2; - private static final int ICMP6_RA_CHECKSUM_LEN = 2; - private static final int ICMP6_RA_OPTION_OFFSET = - ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN; - private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET = - ETH_HEADER_LEN + IPV6_HEADER_LEN + 6; - private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2; - // Prefix information option. - private static final int ICMP6_PREFIX_OPTION_TYPE = 3; - private static final int ICMP6_PREFIX_OPTION_LEN = 32; - private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; - private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4; - private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8; - private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4; - - // From RFC6106: Recursive DNS Server option - private static final int ICMP6_RDNSS_OPTION_TYPE = 25; - // From RFC6106: DNS Search List option - private static final int ICMP6_DNSSL_OPTION_TYPE = 31; - - // From RFC4191: Route Information option - private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; - // Above three options all have the same format: - private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4; - private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; - - // Note: mPacket's position() cannot be assumed to be reset. - private final ByteBuffer mPacket; - - // List of sections in the packet. - private final ArrayList<PacketSection> mPacketSections = new ArrayList<>(); - - // Router lifetime in packet - private final int mRouterLifetime; - // Minimum valid lifetime of PIOs in packet, Long.MAX_VALUE means not seen. - private long mMinPioValidLifetime = Long.MAX_VALUE; - // Minimum route lifetime of RIOs in packet, Long.MAX_VALUE means not seen. - private long mMinRioRouteLifetime = Long.MAX_VALUE; - // Minimum lifetime of RDNSSs in packet, Long.MAX_VALUE means not seen. - private long mMinRdnssLifetime = Long.MAX_VALUE; - // Minimum lifetime in packet - long mMinLifetime; - // When the packet was last captured, in seconds since Unix Epoch - long mLastSeen; - - // For debugging only. Offsets into the packet where PIOs are. - private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>(); - - // For debugging only. Offsets into the packet where RDNSS options are. - private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>(); - - // For debugging only. Offsets into the packet where RIO options are. - private final ArrayList<Integer> mRioOptionOffsets = new ArrayList<>(); - - // For debugging only. How many times this RA was seen. - int seenCount = 0; - - // For debugging only. Returns the hex representation of the last matching packet. - String getLastMatchingPacket() { - return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(), - false /* lowercase */); - } - - // For debugging only. Returns the string representation of the IPv6 address starting at - // position pos in the packet. - private String IPv6AddresstoString(int pos) { - try { - byte[] array = mPacket.array(); - // Can't just call copyOfRange() and see if it throws, because if it reads past the - // end it pads with zeros instead of throwing. - if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) { - return "???"; - } - byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16); - InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes); - return address.getHostAddress(); - } catch (UnsupportedOperationException e) { - // array() failed. Cannot happen, mPacket is array-backed and read-write. - return "???"; - } catch (ClassCastException|UnknownHostException e) { - // Cannot happen. - return "???"; - } - } - - // Can't be static because it's in a non-static inner class. - // TODO: Make this static once RA is its own class. - private void prefixOptionToString(StringBuffer sb, int offset) { - String prefix = IPv6AddresstoString(offset + 16); - int length = getUint8(mPacket, offset + 2); - long valid = getUint32(mPacket, offset + 4); - long preferred = getUint32(mPacket, offset + 8); - sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred)); - } - - private void rdnssOptionToString(StringBuffer sb, int offset) { - int optLen = getUint8(mPacket, offset + 1) * 8; - if (optLen < 24) return; // Malformed or empty. - long lifetime = getUint32(mPacket, offset + 4); - int numServers = (optLen - 8) / 16; - sb.append("DNS ").append(lifetime).append("s"); - for (int server = 0; server < numServers; server++) { - sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server)); - } - sb.append(" "); - } - - private void rioOptionToString(StringBuffer sb, int offset) { - int optLen = getUint8(mPacket, offset + 1) * 8; - if (optLen < 8 || optLen > 24) return; // Malformed or empty. - int prefixLen = getUint8(mPacket, offset + 2); - long lifetime = getUint32(mPacket, offset + 4); - - // This read is variable length because the prefix can be 0, 8 or 16 bytes long. - // We can't use any of the ByteBuffer#get methods here because they all start reading - // from the buffer's current position. - byte[] prefix = new byte[IPV6_ADDR_LEN]; - System.arraycopy(mPacket.array(), offset + 8, prefix, 0, optLen - 8); - sb.append("RIO ").append(lifetime).append("s "); - try { - InetAddress address = (Inet6Address) InetAddress.getByAddress(prefix); - sb.append(address.getHostAddress()); - } catch (UnknownHostException impossible) { - sb.append("???"); - } - sb.append("/").append(prefixLen).append(" "); - } - - public String toString() { - try { - StringBuffer sb = new StringBuffer(); - sb.append(String.format("RA %s -> %s %ds ", - IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET), - IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET), - getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET))); - for (int i: mPrefixOptionOffsets) { - prefixOptionToString(sb, i); - } - for (int i: mRdnssOptionOffsets) { - rdnssOptionToString(sb, i); - } - for (int i: mRioOptionOffsets) { - rioOptionToString(sb, i); - } - return sb.toString(); - } catch (BufferUnderflowException|IndexOutOfBoundsException e) { - return "<Malformed RA>"; - } - } - - /** - * Add a packet section that should be matched, starting from the current position. - * @param length the length of the section - */ - private void addMatchSection(int length) { - // Don't generate JNEBS instruction for 0 bytes as they will fail the - // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check (where cmp_imm is - // the number of bytes to compare) and immediately pass the packet. - // The code does not attempt to generate such matches, but add a safety - // check to prevent doing so in the presence of bugs or malformed or - // truncated packets. - if (length == 0) return; - mPacketSections.add( - new PacketSection(mPacket.position(), length, PacketSection.Type.MATCH, 0, 0)); - mPacket.position(mPacket.position() + length); - } - - /** - * Add a packet section that should be matched, starting from the current position. - * @param end the offset in the packet before which the section ends - */ - private void addMatchUntil(int end) { - addMatchSection(end - mPacket.position()); - } - - /** - * Add a packet section that should be ignored, starting from the current position. - * @param length the length of the section in bytes - */ - private void addIgnoreSection(int length) { - mPacketSections.add( - new PacketSection(mPacket.position(), length, PacketSection.Type.IGNORE, 0, 0)); - mPacket.position(mPacket.position() + length); - } - - /** - * Add a packet section that represents a lifetime, starting from the current position. - * @param length the length of the section in bytes - * @param optionType the RA option containing this lifetime, or 0 for router lifetime - * @param lifetime the lifetime - */ - private void addLifetimeSection(int length, int optionType, long lifetime) { - mPacketSections.add( - new PacketSection(mPacket.position(), length, PacketSection.Type.LIFETIME, - optionType, lifetime)); - mPacket.position(mPacket.position() + length); - } - - /** - * Adds packet sections for an RA option with a 4-byte lifetime 4 bytes into the option - * @param optionType the RA option that is being added - * @param optionLength the length of the option in bytes - */ - private long add4ByteLifetimeOption(int optionType, int optionLength) { - addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET); - final long lifetime = getUint32(mPacket, mPacket.position()); - addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, optionType, lifetime); - addMatchSection(optionLength - ICMP6_4_BYTE_LIFETIME_OFFSET - - ICMP6_4_BYTE_LIFETIME_LEN); - return lifetime; - } - - /** - * Return the router lifetime of the RA - */ - public int routerLifetime() { - return mRouterLifetime; - } - - /** - * Return the minimum valid lifetime in PIOs - */ - public long minPioValidLifetime() { - return mMinPioValidLifetime; - } - - /** - * Return the minimum route lifetime in RIOs - */ - public long minRioRouteLifetime() { - return mMinRioRouteLifetime; - } - - /** - * Return the minimum lifetime in RDNSSs - */ - public long minRdnssLifetime() { - return mMinRdnssLifetime; - } - - // http://b/66928272 http://b/65056012 - // DnsServerRepository ignores RDNSS servers with lifetimes that are too low. Ignore these - // lifetimes for the purpose of filter lifetime calculations. - private boolean shouldIgnoreLifetime(int optionType, long lifetime) { - return optionType == ICMP6_RDNSS_OPTION_TYPE - && lifetime != 0 && lifetime < mMinRdnssLifetimeSec; - } - - private boolean isRelevantLifetime(PacketSection section) { - return section.type == PacketSection.Type.LIFETIME - && !shouldIgnoreLifetime(section.option, section.lifetime); - } - - // Note that this parses RA and may throw InvalidRaException (from - // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException - // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with - // specifications. - @VisibleForTesting - public Ra(byte[] packet, int length) throws InvalidRaException { - if (length < ICMP6_RA_OPTION_OFFSET) { - throw new InvalidRaException("Not an ICMP6 router advertisement: too short"); - } - - mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length)); - mLastSeen = currentTimeSeconds(); - - // Check packet in case a packet arrives before we attach RA filter - // to our packet socket. b/29586253 - if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 || - getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 || - getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) { - throw new InvalidRaException("Not an ICMP6 router advertisement"); - } - - - RaEvent.Builder builder = new RaEvent.Builder(); - - // Ignore the flow label and low 4 bits of traffic class. - addMatchUntil(IPV6_FLOW_LABEL_OFFSET); - addIgnoreSection(IPV6_FLOW_LABEL_LEN); - - // Ignore checksum. - addMatchUntil(ICMP6_RA_CHECKSUM_OFFSET); - addIgnoreSection(ICMP6_RA_CHECKSUM_LEN); - - // Parse router lifetime - addMatchUntil(ICMP6_RA_ROUTER_LIFETIME_OFFSET); - mRouterLifetime = getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET); - addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, mRouterLifetime); - builder.updateRouterLifetime(mRouterLifetime); - - // Add remaining fields (reachable time and retransmission timer) to match section. - addMatchUntil(ICMP6_RA_OPTION_OFFSET); - - while (mPacket.hasRemaining()) { - final int position = mPacket.position(); - final int optionType = getUint8(mPacket, position); - final int optionLength = getUint8(mPacket, position + 1) * 8; - long lifetime; - switch (optionType) { - case ICMP6_PREFIX_OPTION_TYPE: - mPrefixOptionOffsets.add(position); - - // Parse valid lifetime - addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET); - lifetime = getUint32(mPacket, mPacket.position()); - addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN, - ICMP6_PREFIX_OPTION_TYPE, lifetime); - builder.updatePrefixValidLifetime(lifetime); - mMinPioValidLifetime = getMinForPositiveValue( - mMinPioValidLifetime, lifetime); - - // Parse preferred lifetime - lifetime = getUint32(mPacket, mPacket.position()); - addLifetimeSection(ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN, - ICMP6_PREFIX_OPTION_TYPE, lifetime); - builder.updatePrefixPreferredLifetime(lifetime); - - addMatchSection(4); // Reserved bytes - addMatchSection(IPV6_ADDR_LEN); // The prefix itself - break; - // These three options have the same lifetime offset and size, and - // are processed with the same specialized add4ByteLifetimeOption: - case ICMP6_RDNSS_OPTION_TYPE: - mRdnssOptionOffsets.add(position); - lifetime = add4ByteLifetimeOption(optionType, optionLength); - builder.updateRdnssLifetime(lifetime); - mMinRdnssLifetime = getMinForPositiveValue(mMinRdnssLifetime, lifetime); - break; - case ICMP6_ROUTE_INFO_OPTION_TYPE: - mRioOptionOffsets.add(position); - lifetime = add4ByteLifetimeOption(optionType, optionLength); - builder.updateRouteInfoLifetime(lifetime); - mMinRioRouteLifetime = getMinForPositiveValue( - mMinRioRouteLifetime, lifetime); - break; - case ICMP6_DNSSL_OPTION_TYPE: - lifetime = add4ByteLifetimeOption(optionType, optionLength); - builder.updateDnsslLifetime(lifetime); - break; - default: - // RFC4861 section 4.2 dictates we ignore unknown options for forwards - // compatibility. - mPacket.position(position + optionLength); - break; - } - if (optionLength <= 0) { - throw new InvalidRaException(String.format( - "Invalid option length opt=%d len=%d", optionType, optionLength)); - } - } - mMinLifetime = minLifetime(); - mMetricsLog.log(builder.build()); - } - - // Considering only the MATCH sections, does {@code packet} match this RA? - boolean matches(byte[] packet, int length) { - if (length != mPacket.capacity()) return false; - byte[] referencePacket = mPacket.array(); - for (PacketSection section : mPacketSections) { - if (section.type != PacketSection.Type.MATCH) continue; - for (int i = section.start; i < (section.start + section.length); i++) { - if (packet[i] != referencePacket[i]) return false; - } - } - return true; - } - - // What is the minimum of all lifetimes within {@code packet} in seconds? - // Precondition: matches(packet, length) already returned true. - long minLifetime() { - long minLifetime = Long.MAX_VALUE; - for (PacketSection section : mPacketSections) { - if (isRelevantLifetime(section)) { - minLifetime = Math.min(minLifetime, section.lifetime); - } - } - return minLifetime; - } - - // How many seconds does this RA's have to live, taking into account the fact - // that we might have seen it a while ago. - long currentLifetime() { - return mMinLifetime - (currentTimeSeconds() - mLastSeen); - } - - boolean isExpired() { - // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll - // have to calculate the filter lifetime specially as a fraction of 0 is still 0. - return currentLifetime() <= 0; - } - - // Filter for a fraction of the lifetime and adjust for the age of the RA. - @GuardedBy("LegacyApfFilter.this") - int filterLifetime() { - return (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER) - - (int) (mProgramBaseTime - mLastSeen); - } - - @GuardedBy("LegacyApfFilter.this") - boolean shouldFilter() { - return filterLifetime() > 0; - } - - // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped. - // Jump to the next filter if packet doesn't match this RA. - // Return Long.MAX_VALUE if we don't install any filter program for this RA. As the return - // value of this function is used to calculate the program min lifetime (which corresponds - // to the smallest generated filter lifetime). Returning Long.MAX_VALUE in the case no - // filter gets generated makes sure the program lifetime stays unaffected. - @GuardedBy("LegacyApfFilter.this") - long generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { - String nextFilterLabel = "Ra" + getUniqueNumberLocked(); - // Skip if packet is not the right size - gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE); - gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel); - // Skip filter if expired - gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); - gen.addJumpIfR0GreaterThan(filterLifetime(), nextFilterLabel); - for (PacketSection section : mPacketSections) { - // Generate code to match the packet bytes. - if (section.type == PacketSection.Type.MATCH) { - gen.addLoadImmediate(R0, section.start); - gen.addJumpIfBytesAtR0NotEqual( - Arrays.copyOfRange(mPacket.array(), section.start, - section.start + section.length), - nextFilterLabel); - } - - // Generate code to test the lifetimes haven't gone down too far. - // The packet is accepted if any non-ignored lifetime is lower than filterLifetime. - if (isRelevantLifetime(section)) { - switch (section.length) { - case 4: gen.addLoad32(R0, section.start); break; - case 2: gen.addLoad16(R0, section.start); break; - default: - throw new IllegalStateException( - "bogus lifetime size " + section.length); - } - gen.addJumpIfR0LessThan(filterLifetime(), nextFilterLabel); - } - } - maybeSetupCounter(gen, Counter.DROPPED_RA); - gen.addJump(mCountAndDropLabel); - gen.defineLabel(nextFilterLabel); - return filterLifetime(); - } - } - - // TODO: Refactor these subclasses to avoid so much repetition. - private abstract static class KeepalivePacket { - // Note that the offset starts from IP header. - // These must be added ether header length when generating program. - static final int IP_HEADER_OFFSET = 0; - static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; - - // Append a filter for this keepalive ack to {@code gen}. - // Jump to drop if it matches the keepalive ack. - // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException; - } - - // A class to hold NAT-T keepalive ack information. - private class NattKeepaliveResponse extends KeepalivePacket { - static final int UDP_LENGTH_OFFSET = 4; - static final int UDP_HEADER_LEN = 8; - - protected class NattKeepaliveResponseData { - public final byte[] srcAddress; - public final int srcPort; - public final byte[] dstAddress; - public final int dstPort; - - NattKeepaliveResponseData(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { - srcAddress = sentKeepalivePacket.dstAddress; - srcPort = sentKeepalivePacket.dstPort; - dstAddress = sentKeepalivePacket.srcAddress; - dstPort = sentKeepalivePacket.srcPort; - } - } - - protected final NattKeepaliveResponseData mPacket; - protected final byte[] mSrcDstAddr; - protected final byte[] mPortFingerprint; - // NAT-T keepalive packet - protected final byte[] mPayload = {(byte) 0xff}; - - NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { - mPacket = new NattKeepaliveResponseData(sentKeepalivePacket); - mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress); - mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort); - } - - byte[] generatePortFingerprint(int srcPort, int dstPort) { - final ByteBuffer fp = ByteBuffer.allocate(4); - fp.order(ByteOrder.BIG_ENDIAN); - fp.putShort((short) srcPort); - fp.putShort((short) dstPort); - return fp.array(); - } - - @Override - @GuardedBy("LegacyApfFilter.this") - void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { - final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); - - gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); - - // A NAT-T keepalive packet contains 1 byte payload with the value 0xff - // Check payload length is 1 - gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); - gen.addAdd(UDP_HEADER_LEN); - gen.addSwap(); - gen.addLoad16(R0, IPV4_TOTAL_LENGTH_OFFSET); - gen.addNeg(R1); - gen.addAddR1ToR0(); - gen.addJumpIfR0NotEquals(1, nextFilterLabel); - - // Check that the ports match - gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); - gen.addAdd(ETH_HEADER_LEN); - gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel); - - // Payload offset = R0 + UDP header length - gen.addAdd(UDP_HEADER_LEN); - gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel); - - maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE); - gen.addJump(mCountAndDropLabel); - gen.defineLabel(nextFilterLabel); - } - - public String toString() { - try { - return String.format("%s -> %s", - ConnectivityUtils.addressAndPortToString( - InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), - ConnectivityUtils.addressAndPortToString( - InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort)); - } catch (UnknownHostException e) { - return "Unknown host"; - } - } - } - - // A class to hold TCP keepalive ack information. - private abstract static class TcpKeepaliveAck extends KeepalivePacket { - protected static class TcpKeepaliveAckData { - public final byte[] srcAddress; - public final int srcPort; - public final byte[] dstAddress; - public final int dstPort; - public final int seq; - public final int ack; - - // Create the characteristics of the ack packet from the sent keepalive packet. - TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { - srcAddress = sentKeepalivePacket.dstAddress; - srcPort = sentKeepalivePacket.dstPort; - dstAddress = sentKeepalivePacket.srcAddress; - dstPort = sentKeepalivePacket.srcPort; - seq = sentKeepalivePacket.ack; - ack = sentKeepalivePacket.seq + 1; - } - } - - protected final TcpKeepaliveAckData mPacket; - protected final byte[] mSrcDstAddr; - protected final byte[] mPortSeqAckFingerprint; - - TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) { - mPacket = packet; - mSrcDstAddr = srcDstAddr; - mPortSeqAckFingerprint = generatePortSeqAckFingerprint(mPacket.srcPort, - mPacket.dstPort, mPacket.seq, mPacket.ack); - } - - static byte[] generatePortSeqAckFingerprint(int srcPort, int dstPort, int seq, int ack) { - final ByteBuffer fp = ByteBuffer.allocate(12); - fp.order(ByteOrder.BIG_ENDIAN); - fp.putShort((short) srcPort); - fp.putShort((short) dstPort); - fp.putInt(seq); - fp.putInt(ack); - return fp.array(); - } - - public String toString() { - try { - return String.format("%s -> %s , seq=%d, ack=%d", - ConnectivityUtils.addressAndPortToString( - InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), - ConnectivityUtils.addressAndPortToString( - InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort), - Integer.toUnsignedLong(mPacket.seq), - Integer.toUnsignedLong(mPacket.ack)); - } catch (UnknownHostException e) { - return "Unknown host"; - } - } - - // Append a filter for this keepalive ack to {@code gen}. - // Jump to drop if it matches the keepalive ack. - // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException; - } - - private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { - - TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { - this(new TcpKeepaliveAckData(sentKeepalivePacket)); - } - TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) { - super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); - } - - @Override - @GuardedBy("LegacyApfFilter.this") - void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { - final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); - - gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); - - // Skip to the next filter if it's not zero-sized : - // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0 - // Load the IP header size into R1 - gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); - // Load the TCP header size into R0 (it's indexed by R1) - gen.addLoad8Indexed(R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET); - // Size offset is in the top nibble, but it must be multiplied by 4, and the two - // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2. - gen.addRightShift(2); - // R0 += R1 -> R0 contains TCP + IP headers length - gen.addAddR1ToR0(); - // Load IPv4 total length - gen.addLoad16(R1, IPV4_TOTAL_LENGTH_OFFSET); - gen.addNeg(R0); - gen.addAddR1ToR0(); - gen.addJumpIfR0NotEquals(0, nextFilterLabel); - // Add IPv4 header length - gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); - gen.addLoadImmediate(R0, ETH_HEADER_LEN); - gen.addAddR1ToR0(); - gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel); - - maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); - gen.addJump(mCountAndDropLabel); - gen.defineLabel(nextFilterLabel); - } - } - - private class TcpKeepaliveAckV6 extends TcpKeepaliveAck { - TcpKeepaliveAckV6(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { - this(new TcpKeepaliveAckData(sentKeepalivePacket)); - } - TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) { - super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); - } - - @Override - void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { - throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet"); - } - } - - // Maximum number of RAs to filter for. - private static final int MAX_RAS = 10; - - @GuardedBy("this") - private ArrayList<Ra> mRas = new ArrayList<>(); - @GuardedBy("this") - private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>(); - @GuardedBy("this") - private final List<String[]> mMdnsAllowList = new ArrayList<>(); - - // There is always some marginal benefit to updating the installed APF program when an RA is - // seen because we can extend the program's lifetime slightly, but there is some cost to - // updating the program, so don't bother unless the program is going to expire soon. This - // constant defines "soon" in seconds. - private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30; - // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever - // see a refresh. Using half the lifetime might be a good idea except for the fact that - // packets may be dropped, so let's use 6. - private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6; - - // The base time for this filter program. In seconds since Unix Epoch. - // This is the time when the APF program was generated. All filters in the program should use - // this base time as their current time for consistency purposes. - @GuardedBy("this") - private long mProgramBaseTime; - // When did we last install a filter program? In seconds since Unix Epoch. - @GuardedBy("this") - private long mLastTimeInstalledProgram; - // How long should the last installed filter program live for? In seconds. - @GuardedBy("this") - private long mLastInstalledProgramMinLifetime; - @GuardedBy("this") - private ApfProgramEvent.Builder mLastInstallEvent; - - // For debugging only. The last program installed. - @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; - // The maximum program size that updated since we started. - @GuardedBy("this") - private int mMaxProgramSize = 0; - // The maximum number of distinct RAs - @GuardedBy("this") - private int mMaxDistinctRas = 0; - // How many times the program was updated since we started for allowing multicast traffic. - @GuardedBy("this") - private int mNumProgramUpdatesAllowingMulticast = 0; - - /** - * Generate filter code to process ARP packets. Execution of this code ends in either the - * DROP_LABEL or PASS_LABEL and does not fall off the end. - * Preconditions: - * - Packet being filtered is ARP - */ - @GuardedBy("this") - private void generateArpFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { - // Here's a basic summary of what the ARP filter program does: - // - // if not ARP IPv4 - // pass - // if not ARP IPv4 reply or request - // pass - // if ARP reply source ip is 0.0.0.0 - // drop - // if unicast ARP reply - // pass - // if interface has no IPv4 address - // if target ip is 0.0.0.0 - // drop - // else - // if target ip is not the interface ip - // drop - // pass - - final String checkTargetIPv4 = "checkTargetIPv4"; - - // Pass if not ARP IPv4. - gen.addLoadImmediate(R0, ARP_HEADER_OFFSET); - maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4); - gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel); - - // Pass if unknown ARP opcode. - gen.addLoad16(R0, ARP_OPCODE_OFFSET); - gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check - maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN); - gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel); - - // Drop if ARP reply source IP is 0.0.0.0 - gen.addLoad32(R0, ARP_SOURCE_IP_ADDRESS_OFFSET); - maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST); - gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel); - - // Pass if unicast reply. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY); - gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, 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(R0, ARP_TARGET_IP_ADDRESS_OFFSET); - maybeSetupCounter(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(R0, ARP_TARGET_IP_ADDRESS_OFFSET); - maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST); - gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel); - } - - maybeSetupCounter(gen, Counter.PASSED_ARP); - gen.addJump(mCountAndPassLabel); - } - - /** - * Generate filter code to process IPv4 packets. Execution of this code ends in either the - * DROP_LABEL or PASS_LABEL and does not fall off the end. - * Preconditions: - * - Packet being filtered is IPv4 - */ - @GuardedBy("this") - private void generateIPv4FilterLocked(ApfV4Generator gen) throws IllegalInstructionException { - // Here's a basic summary of what the IPv4 filter program does: - // - // if filtering multicast (i.e. multicast lock not held): - // if it's DHCP destined to our MAC: - // pass - // if it's L2 broadcast: - // drop - // if it's IPv4 multicast: - // drop - // if it's IPv4 broadcast: - // drop - // if keepalive ack - // drop - // pass - - if (mMulticastFilter) { - final String skipDhcpv4Filter = "skip_dhcp_v4_filter"; - - // Pass DHCP addressed to us. - // Check it's UDP. - gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter); - // Check it's not a fragment. This matches the BPF filter installed by the DHCP client. - gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET); - gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter); - // Check it's addressed to DHCP client port. - gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); - gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET); - gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter); - // Check it's DHCP to our MAC address. - gen.addLoadImmediate(R0, DHCP_CLIENT_MAC_OFFSET); - // NOTE: Relies on R1 containing IPv4 header offset. - gen.addAddR1ToR0(); - gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter); - maybeSetupCounter(gen, Counter.PASSED_DHCP); - gen.addJump(mCountAndPassLabel); - - // Drop all multicasts/broadcasts. - gen.defineLabel(skipDhcpv4Filter); - - // If IPv4 destination address is in multicast range, drop. - gen.addLoad8(R0, IPV4_DEST_ADDR_OFFSET); - gen.addAnd(0xf0); - maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST); - gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel); - - // If IPv4 broadcast packet, drop regardless of L2 (b/30231088). - maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR); - gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET); - gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel); - if (mIPv4Address != null && mIPv4PrefixLength < 31) { - maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET); - int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength); - gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); - } - - // If any TCP keepalive filter matches, drop - generateV4KeepaliveFilters(gen); - - // If any NAT-T keepalive filter matches, drop - generateV4NattKeepaliveFilters(gen); - - // Otherwise, this is an IPv4 unicast, pass - // If L2 broadcast packet, drop. - // TODO: can we invert this condition to fall through to the common pass case below? - maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); - maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); - gen.addJump(mCountAndDropLabel); - } else { - generateV4KeepaliveFilters(gen); - generateV4NattKeepaliveFilters(gen); - } - - // Otherwise, pass - maybeSetupCounter(gen, Counter.PASSED_IPV4); - gen.addJump(mCountAndPassLabel); - } - - @GuardedBy("this") - private void generateKeepaliveFilters(ApfV4Generator gen, Class<?> filterType, int proto, - int offset, String label) throws IllegalInstructionException { - final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets, - ack -> filterType.isInstance(ack)); - - // If no keepalive packets of this type - if (!haveKeepaliveResponses) return; - - // If not the right proto, skip keepalive filters - gen.addLoad8(R0, offset); - gen.addJumpIfR0NotEquals(proto, label); - - // Drop Keepalive responses - for (int i = 0; i < mKeepalivePackets.size(); ++i) { - final KeepalivePacket response = mKeepalivePackets.valueAt(i); - if (filterType.isInstance(response)) response.generateFilterLocked(gen); - } - - gen.defineLabel(label); - } - - @GuardedBy("this") - private void generateV4KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { - generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET, - "skip_v4_keepalive_filter"); - } - - @GuardedBy("this") - private void generateV4NattKeepaliveFilters(ApfV4Generator gen) - throws IllegalInstructionException { - generateKeepaliveFilters(gen, NattKeepaliveResponse.class, - IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter"); - } - - /** - * Generate filter code to process IPv6 packets. Execution of this code ends in either the - * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets. - * Preconditions: - * - Packet being filtered is IPv6 - */ - @GuardedBy("this") - private void generateIPv6FilterLocked(ApfV4Generator gen) throws IllegalInstructionException { - // Here's a basic summary of what the IPv6 filter program does: - // - // if there is a hop-by-hop option present (e.g. MLD query) - // pass - // if we're dropping multicast - // if it's not IPCMv6 or it's ICMPv6 but we're in doze mode: - // if it's multicast: - // drop - // pass - // if it's ICMPv6 RS to any: - // drop - // if it's ICMPv6 NA to anything in ff02::/120 - // drop - // if keepalive ack - // drop - - gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET); - - // MLD packets set the router-alert hop-by-hop option. - // TODO: be smarter about not blindly passing every packet with HBH options. - maybeSetupCounter(gen, Counter.PASSED_MLD); - gen.addJumpIfR0Equals(IPPROTO_HOPOPTS, mCountAndPassLabel); - - // Drop multicast if the multicast filter is enabled. - if (mMulticastFilter) { - final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter"; - final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast"; - - // While in doze mode, drop ICMPv6 multicast pings, let the others pass. - // While awake, let all ICMPv6 multicasts through. - if (mInDozeMode) { - // Not ICMPv6? -> Proceed to multicast filtering - gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel); - - // ICMPv6 but not ECHO? -> Skip the multicast filter. - // (ICMPv6 ECHO requests will go through the multicast filter below). - gen.addLoad8(R0, ICMP6_TYPE_OFFSET); - gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel); - } else { - gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel); - } - - // Drop all other packets sent to ff00::/8 (multicast prefix). - gen.defineLabel(dropAllIPv6MulticastsLabel); - maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST); - gen.addLoad8(R0, IPV6_DEST_ADDR_OFFSET); - gen.addJumpIfR0Equals(0xff, mCountAndDropLabel); - // If any keepalive filter matches, drop - generateV6KeepaliveFilters(gen); - // Not multicast. Pass. - maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP); - gen.addJump(mCountAndPassLabel); - gen.defineLabel(skipIPv6MulticastFilterLabel); - } else { - generateV6KeepaliveFilters(gen); - // If not ICMPv6, pass. - maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP); - gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel); - } - - // If we got this far, the packet is ICMPv6. Drop some specific types. - - // Add unsolicited multicast neighbor announcements filter - String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA"; - gen.addLoad8(R0, ICMP6_TYPE_OFFSET); - // Drop all router solicitations (b/32833400) - maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION); - gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel); - // If not neighbor announcements, skip filter. - gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel); - // Drop all multicast NA to ff02::/120. - // This is a way to cover ff02::1 and ff02::2 with a single JNEBS. - // TODO: Drop only if they don't contain the address of on-link neighbours. - final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15); - gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET); - gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel); - - maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); - gen.addJump(mCountAndDropLabel); - gen.defineLabel(skipUnsolicitedMulticastNALabel); - } - - /** Encodes qname in TLV pattern. */ - @VisibleForTesting - public static byte[] encodeQname(String[] labels) { - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - for (String label : labels) { - byte[] labelBytes = label.getBytes(StandardCharsets.UTF_8); - out.write(labelBytes.length); - out.write(labelBytes, 0, labelBytes.length); - } - out.write(0); - return out.toByteArray(); - } - - /** - * Generate filter code to process mDNS packets. Execution of this code ends in * DROP_LABEL - * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check. - */ - @GuardedBy("this") - private void generateMdnsFilterLocked(ApfV4Generator gen) - throws IllegalInstructionException { - final String skipMdnsv4Filter = "skip_mdns_v4_filter"; - final String skipMdnsFilter = "skip_mdns_filter"; - final String checkMdnsUdpPort = "check_mdns_udp_port"; - final String mDnsAcceptPacket = "mdns_accept_packet"; - final String mDnsDropPacket = "mdns_drop_packet"; - - // Only turn on the filter if multicast filter is on and the qname allowlist is non-empty. - if (!mMulticastFilter || mMdnsAllowList.isEmpty()) { - return; - } - - // Here's a basic summary of what the mDNS filter program does: - // - // if it is a multicast mDNS packet - // if QDCOUNT != 1 - // pass - // else if the QNAME is in the allowlist - // pass - // else: - // drop - // - // A packet is considered as a multicast mDNS packet if it matches all the following - // conditions - // 1. its destination MAC address matches 01:00:5E:00:00:FB or 33:33:00:00:00:FB, for - // v4 and v6 respectively. - // 2. it is an IPv4/IPv6 packet - // 3. it is a UDP packet with port 5353 - - // Check it's L2 mDNS multicast address. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, - skipMdnsv4Filter); - - // Checks it's IPv4. - gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET); - gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter); - - // Checks it's UDP. - gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter); - // Set R1 to IPv4 header. - gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE); - gen.addJump(checkMdnsUdpPort); - - gen.defineLabel(skipMdnsv4Filter); - - // Checks it's L2 mDNS multicast address. - // Relies on R0 containing the ethernet destination mac address offset. - gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter); - - // Checks it's IPv6. - gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET); - gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter); - - // Checks it's UDP. - gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter); - - // Set R1 to IPv6 header. - gen.addLoadImmediate(R1, IPV6_HEADER_LEN); - - // Checks it's mDNS UDP port - gen.defineLabel(checkMdnsUdpPort); - gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET); - gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter); - - gen.addLoad16Indexed(R0, MDNS_QDCOUNT_OFFSET); - // If QDCOUNT != 1, pass the packet - gen.addJumpIfR0NotEquals(1, mDnsAcceptPacket); - - // If QDCOUNT == 1, matches the QNAME with allowlist. - // Load offset for the first QNAME. - gen.addLoadImmediate(R0, MDNS_QNAME_OFFSET); - gen.addAddR1ToR0(); - - // Check first QNAME against allowlist - for (int i = 0; i < mMdnsAllowList.size(); ++i) { - final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i; - final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i)); - gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck); - // QNAME matched - gen.addJump(mDnsAcceptPacket); - // QNAME not matched - gen.defineLabel(mDnsNextAllowedQnameCheck); - } - // If QNAME doesn't match any entries in allowlist, drop the packet. - gen.defineLabel(mDnsDropPacket); - maybeSetupCounter(gen, Counter.DROPPED_MDNS); - gen.addJump(mCountAndDropLabel); - - gen.defineLabel(mDnsAcceptPacket); - maybeSetupCounter(gen, Counter.PASSED_MDNS); - gen.addJump(mCountAndPassLabel); - - - gen.defineLabel(skipMdnsFilter); - } - - @GuardedBy("this") - private void generateV6KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { - generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, - "skip_v6_keepalive_filter"); - } - - /** - * Begin generating an APF program to: - * <ul> - * <li>Drop/Pass 802.3 frames (based on policy) - * <li>Drop packets with EtherType within the Black List - * <li>Drop ARP requests not for us, if mIPv4Address is set, - * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC, - * <li>Drop IPv4 multicast packets, if mMulticastFilter, - * <li>Pass all other IPv4 packets, - * <li>Drop all broadcast non-IP non-ARP packets. - * <li>Pass all non-ICMPv6 IPv6 packets, - * <li>Pass all non-IPv4 and non-IPv6 packets, - * <li>Drop IPv6 ICMPv6 NAs to anything in ff02::/120. - * <li>Drop IPv6 ICMPv6 RSs. - * <li>Filter IPv4 packets (see generateIPv4FilterLocked()) - * <li>Filter IPv6 packets (see generateIPv6FilterLocked()) - * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows - * insertion of RA filters here, or if there aren't any, just passes the packets. - * </ul> - */ - @GuardedBy("this") - protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException { - // This is guaranteed to succeed because of the check in maybeCreate. - ApfV4Generator gen = new ApfV4Generator(mApfVersionSupported, mMaximumApfProgramSize, - mMaximumApfProgramSize); - - if (hasDataAccess(mApfVersionSupported)) { - // Increment TOTAL_PACKETS - maybeSetupCounter(gen, Counter.TOTAL_PACKETS); - gen.addLoadData(R0, 0); // load counter - gen.addAdd(1); - gen.addStoreData(R0, 0); // write-back counter - - maybeSetupCounter(gen, Counter.FILTER_AGE_SECONDS); - gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); - gen.addStoreData(R0, 0); // store 'counter' - - // requires a new enough APFv5+ interpreter, otherwise will be 0 - maybeSetupCounter(gen, Counter.FILTER_AGE_16384THS); - gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS); - gen.addStoreData(R0, 0); // store 'counter' - - // requires a new enough APFv5+ interpreter, otherwise will be 0 - maybeSetupCounter(gen, Counter.APF_VERSION); - gen.addLoadFromMemory(R0, MemorySlot.APF_VERSION); - gen.addStoreData(R0, 0); // store 'counter' - - // store this program's sequential id, for later comparison - maybeSetupCounter(gen, Counter.APF_PROGRAM_ID); - gen.addLoadImmediate(R0, mNumProgramUpdates); - gen.addStoreData(R0, 0); // store 'counter' - } - - // Here's a basic summary of what the initial program does: - // - // if it's a 802.3 Frame (ethtype < 0x0600): - // drop or pass based on configurations - // if it has a ether-type that belongs to the black list - // drop - // if it's ARP: - // insert ARP filter to drop or pass these appropriately - // if it's IPv4: - // insert IPv4 filter to drop or pass these appropriately - // if it's not IPv6: - // if it's broadcast: - // drop - // pass - // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets - - gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET); - - if (mDrop802_3Frames) { - // drop 802.3 frames (ethtype < 0x0600) - maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME); - gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel); - } - - // Handle ether-type black list - maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_NOT_ALLOWED); - for (int p : mEthTypeBlackList) { - gen.addJumpIfR0Equals(p, mCountAndDropLabel); - } - - // Add ARP filters: - String skipArpFiltersLabel = "skipArpFilters"; - gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel); - generateArpFilterLocked(gen); - gen.defineLabel(skipArpFiltersLabel); - - // Add mDNS filter: - generateMdnsFilterLocked(gen); - gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET); - - // Add IPv4 filters: - String skipIPv4FiltersLabel = "skipIPv4Filters"; - gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel); - generateIPv4FilterLocked(gen); - gen.defineLabel(skipIPv4FiltersLabel); - - // Check for IPv6: - // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did - // not execute the IPv4 filter, since this filter do not fall through, but either drop or - // pass. - String ipv6FilterLabel = "IPv6Filters"; - gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel); - - // Drop non-IP non-ARP broadcasts, pass the rest - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST); - gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); - maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST); - gen.addJump(mCountAndDropLabel); - - // Add IPv6 filters: - gen.defineLabel(ipv6FilterLabel); - generateIPv6FilterLocked(gen); - 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(ApfV4Generator 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 (!hasDataAccess(mApfVersionSupported)) return; - - // Execution will reach the bottom of the program if none of the filters match, - // which will pass the packet to the application processor. - maybeSetupCounter(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(R0, 0); // R0 = *(R1 + 0) - gen.addAdd(1); // R0++ - gen.addStoreData(R0, 0); // *(R1 + 0) = R0 - gen.addJump(gen.PASS_LABEL); - - // Same as above for the count & drop trampoline. - gen.defineLabel(mCountAndDropLabel); - gen.addLoadData(R0, 0); // R0 = *(R1 + 0) - gen.addAdd(1); // R0++ - gen.addStoreData(R0, 0); // *(R1 + 0) = R0 - gen.addJump(gen.DROP_LABEL); - } - - /** - * Generate and install a new filter program. - */ - @GuardedBy("this") - // errorprone false positive on ra#shouldFilter and ra#generateFilterLocked - @SuppressWarnings("GuardedBy") - @VisibleForTesting - public void installNewProgramLocked() { - purgeExpiredRasLocked(); - ArrayList<Ra> rasToFilter = new ArrayList<>(); - final byte[] program; - long programMinLifetime = Long.MAX_VALUE; - long maximumApfProgramSize = mMaximumApfProgramSize; - if (hasDataAccess(mApfVersionSupported)) { - // Reserve space for the counters. - maximumApfProgramSize -= Counter.totalSize(); - } - - mProgramBaseTime = currentTimeSeconds(); - try { - // Step 1: Determine how many RA filters we can fit in the program. - ApfV4Generator 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); - sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); - return; - } - - for (Ra ra : mRas) { - if (!ra.shouldFilter()) continue; - ra.generateFilterLocked(gen); - // Stop if we get too big. - if (gen.programLengthOverEstimate() > maximumApfProgramSize) { - if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs"); - sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); - break; - } - - rasToFilter.add(ra); - } - - // Step 2: Actually generate the program - gen = emitPrologueLocked(); - for (Ra ra : rasToFilter) { - programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen)); - } - emitEpilogue(gen); - program = gen.generate(); - } catch (IllegalInstructionException|IllegalStateException e) { - Log.e(TAG, "Failed to generate APF program.", e); - sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); - return; - } - if (mIsRunning && !mIpClientCallback.installPacketFilter(program)) { - sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); - } - mLastTimeInstalledProgram = mProgramBaseTime; - mLastInstalledProgramMinLifetime = programMinLifetime; - mLastInstalledProgram = program; - mNumProgramUpdates++; - mMaxProgramSize = Math.max(mMaxProgramSize, program.length); - - if (VDBG) { - hexDump("Installing filter: ", program, program.length); - } - logApfProgramEventLocked(mProgramBaseTime); - mLastInstallEvent = new ApfProgramEvent.Builder() - .setLifetime(programMinLifetime) - .setFilteredRas(rasToFilter.size()) - .setCurrentRas(mRas.size()) - .setProgramLength(program.length) - .setFlags(mIPv4Address != null, mMulticastFilter); - } - - @GuardedBy("this") - private void logApfProgramEventLocked(long now) { - if (mLastInstallEvent == null) { - return; - } - ApfProgramEvent.Builder ev = mLastInstallEvent; - mLastInstallEvent = null; - final long actualLifetime = now - mLastTimeInstalledProgram; - ev.setActualLifetime(actualLifetime); - if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) { - return; - } - mMetricsLog.log(ev.build()); - } - - /** - * Returns {@code true} if a new program should be installed because the current one dies soon. - */ - @GuardedBy("this") - private boolean shouldInstallnewProgram() { - long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime; - return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING; - } - - private void hexDump(String msg, byte[] packet, int length) { - log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */)); - } - - @GuardedBy("this") - private void purgeExpiredRasLocked() { - for (int i = 0; i < mRas.size();) { - if (mRas.get(i).isExpired()) { - log("Expiring " + mRas.get(i)); - mRas.remove(i); - } else { - i++; - } - } - } - - // Get the minimum value excludes zero. This is used for calculating the lowest lifetime values - // in RA packets. Zero lifetimes are excluded because we want to detect whether there is any - // unusually small lifetimes but zero lifetime is actually valid (cease to be a default router - // or the option is no longer be used). Number of zero lifetime RAs is collected in a different - // Metrics. - private long getMinForPositiveValue(long oldMinValue, long value) { - if (value < 1) return oldMinValue; - return Math.min(oldMinValue, value); - } - - private int getMinForPositiveValue(int oldMinValue, int value) { - return (int) getMinForPositiveValue((long) oldMinValue, (long) value); - } - - /** - * 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. - */ - @VisibleForTesting - public synchronized ProcessRaResult processRa(byte[] packet, int length) { - if (VDBG) hexDump("Read packet = ", packet, length); - - // Have we seen this RA before? - for (int i = 0; i < mRas.size(); i++) { - Ra ra = mRas.get(i); - if (ra.matches(packet, length)) { - if (VDBG) log("matched RA " + ra); - // Update lifetimes. - ra.mLastSeen = currentTimeSeconds(); - ra.seenCount++; - - // Keep mRas in LRU order so as to prioritize generating filters for recently seen - // RAs. LRU prioritizes this because RA filters are generated in order from mRas - // until the filter program exceeds the maximum filter program size allowed by the - // chipset, so RAs appearing earlier in mRas are more likely to make it into the - // filter program. - // TODO: consider sorting the RAs in order of increasing expiry time as well. - // Swap to front of array. - mRas.add(0, mRas.remove(i)); - - // If the current program doesn't expire for a while, don't update. - if (shouldInstallnewProgram()) { - installNewProgramLocked(); - return ProcessRaResult.UPDATE_EXPIRY; - } - return ProcessRaResult.MATCH; - } - } - purgeExpiredRasLocked(); - - mMaxDistinctRas = Math.max(mMaxDistinctRas, mRas.size() + 1); - - // TODO: figure out how to proceed when we've received more than MAX_RAS RAs. - 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); - mNumParseErrorRas++; - return ProcessRaResult.PARSE_ERROR; - } - - // Update info for Metrics - mLowestRouterLifetimeSeconds = getMinForPositiveValue( - mLowestRouterLifetimeSeconds, ra.routerLifetime()); - mLowestPioValidLifetimeSeconds = getMinForPositiveValue( - mLowestPioValidLifetimeSeconds, ra.minPioValidLifetime()); - mLowestRioRouteLifetimeSeconds = getMinForPositiveValue( - mLowestRioRouteLifetimeSeconds, ra.minRioRouteLifetime()); - mLowestRdnssLifetimeSeconds = getMinForPositiveValue( - mLowestRdnssLifetimeSeconds, ra.minRdnssLifetime()); - - // Ignore 0 lifetime RAs. - if (ra.isExpired()) { - mNumZeroLifetimeRas++; - return ProcessRaResult.ZERO_LIFETIME; - } - log("Adding " + ra); - mRas.add(ra); - installNewProgramLocked(); - return ProcessRaResult.UPDATE_NEW_RA; - } - - /** - * Create an {@link LegacyApfFilter} if {@code apfCapabilities} indicates support for packet - * filtering using APF programs. - */ - public static LegacyApfFilter maybeCreate(Context context, ApfFilter.ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, - NetworkQuirkMetrics networkQuirkMetrics) { - if (context == null || config == null || ifParams == null) return null; - if (!ApfV4Generator.supportsVersion(config.apfVersionSupported)) { - return null; - } - if (config.apfRamSize < 512) { - Log.e(TAG, "Unacceptably small APF limit: " + config.apfRamSize); - return null; - } - - return new LegacyApfFilter(context, config, ifParams, ipClientCallback, - new IpConnectivityLog(), networkQuirkMetrics); - } - - private synchronized void collectAndSendMetrics() { - if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return; - final long sessionDurationMs = mClock.elapsedRealtime() - mSessionStartMs; - if (sessionDurationMs < mMinMetricsSessionDurationMs) return; - - // Collect and send IpClientRaInfoMetrics. - mIpClientRaInfoMetrics.setMaxNumberOfDistinctRas(mMaxDistinctRas); - mIpClientRaInfoMetrics.setNumberOfZeroLifetimeRas(mNumZeroLifetimeRas); - mIpClientRaInfoMetrics.setNumberOfParsingErrorRas(mNumParseErrorRas); - mIpClientRaInfoMetrics.setLowestRouterLifetimeSeconds(mLowestRouterLifetimeSeconds); - mIpClientRaInfoMetrics.setLowestPioValidLifetimeSeconds(mLowestPioValidLifetimeSeconds); - mIpClientRaInfoMetrics.setLowestRioRouteLifetimeSeconds(mLowestRioRouteLifetimeSeconds); - mIpClientRaInfoMetrics.setLowestRdnssLifetimeSeconds(mLowestRdnssLifetimeSeconds); - mIpClientRaInfoMetrics.statsWrite(); - - // Collect and send ApfSessionInfoMetrics. - mApfSessionInfoMetrics.setVersion(mApfVersionSupported); - mApfSessionInfoMetrics.setMemorySize(mMaximumApfProgramSize); - mApfSessionInfoMetrics.setApfSessionDurationSeconds( - (int) (sessionDurationMs / DateUtils.SECOND_IN_MILLIS)); - mApfSessionInfoMetrics.setNumOfTimesApfProgramUpdated(mNumProgramUpdates); - mApfSessionInfoMetrics.setMaxProgramSize(mMaxProgramSize); - for (Map.Entry<Counter, Long> entry : mApfCounterTracker.getCounters().entrySet()) { - if (entry.getValue() > 0) { - mApfSessionInfoMetrics.addApfCounter(entry.getKey(), entry.getValue()); - } - } - mApfSessionInfoMetrics.statsWrite(); - } - - public synchronized void shutdown() { - collectAndSendMetrics(); - if (mReceiveThread != null) { - log("shutting down"); - mReceiveThread.halt(); // Also closes socket. - mReceiveThread = null; - } - mRas.clear(); - mContext.unregisterReceiver(mDeviceIdleReceiver); - } - - public synchronized void setMulticastFilter(boolean isEnabled) { - if (mMulticastFilter == isEnabled) return; - mMulticastFilter = isEnabled; - if (!isEnabled) { - mNumProgramUpdatesAllowingMulticast++; - } - installNewProgramLocked(); - } - - /** Adds qname to the mDNS allowlist */ - public synchronized void addToMdnsAllowList(String[] labels) { - mMdnsAllowList.add(labels); - if (mMulticastFilter) { - installNewProgramLocked(); - } - } - - /** Removes qname from the mDNS allowlist */ - public synchronized void removeFromAllowList(String[] labels) { - mMdnsAllowList.removeIf(e -> Arrays.equals(labels, e)); - if (mMulticastFilter) { - installNewProgramLocked(); - } - } - - @VisibleForTesting - public synchronized void setDozeMode(boolean isEnabled) { - if (mInDozeMode == isEnabled) return; - mInDozeMode = isEnabled; - installNewProgramLocked(); - } - - /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */ - private static LinkAddress findIPv4LinkAddress(LinkProperties lp) { - LinkAddress ipv4Address = null; - for (LinkAddress address : lp.getLinkAddresses()) { - if (!(address.getAddress() instanceof Inet4Address)) { - continue; - } - if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) { - // More than one IPv4 address, abort. - return null; - } - ipv4Address = address; - } - return ipv4Address; - } - - public synchronized void setLinkProperties(LinkProperties lp) { - // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state. - final LinkAddress ipv4Address = findIPv4LinkAddress(lp); - final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null; - final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0; - if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) { - return; - } - mIPv4Address = addr; - mIPv4PrefixLength = prefix; - installNewProgramLocked(); - } - - /** - * Add TCP keepalive ack packet filter. - * This will add a filter to drop acks to the keepalive packet passed as an argument. - * - * @param slot The index used to access the filter. - * @param sentKeepalivePacket The attributes of the sent keepalive packet. - */ - public synchronized void addTcpKeepalivePacketFilter(final int slot, - final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { - log("Adding keepalive ack(" + slot + ")"); - if (null != mKeepalivePackets.get(slot)) { - throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied"); - } - final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6; - mKeepalivePackets.put(slot, (ipVersion == 4) - ? new TcpKeepaliveAckV4(sentKeepalivePacket) - : new TcpKeepaliveAckV6(sentKeepalivePacket)); - installNewProgramLocked(); - } - - /** - * Add NAT-T keepalive packet filter. - * This will add a filter to drop NAT-T keepalive packet which is passed as an argument. - * - * @param slot The index used to access the filter. - * @param sentKeepalivePacket The attributes of the sent keepalive packet. - */ - public synchronized void addNattKeepalivePacketFilter(final int slot, - final NattKeepalivePacketDataParcelable sentKeepalivePacket) { - log("Adding NAT-T keepalive packet(" + slot + ")"); - if (null != mKeepalivePackets.get(slot)) { - throw new IllegalArgumentException("NAT-T Keepalive slot " + slot + " is occupied"); - } - - // TODO : update ApfFilter to support dropping v6 keepalives - if (sentKeepalivePacket.srcAddress.length != 4) { - return; - } - - mKeepalivePackets.put(slot, new NattKeepaliveResponse(sentKeepalivePacket)); - installNewProgramLocked(); - } - - /** - * Remove keepalive packet filter. - * - * @param slot The index used to access the filter. - */ - public synchronized void removeKeepalivePacketFilter(int slot) { - log("Removing keepalive packet(" + slot + ")"); - mKeepalivePackets.remove(slot); - installNewProgramLocked(); - } - - public synchronized void dump(IndentingPrintWriter pw) { - pw.println(String.format( - "Capabilities: { apfVersionSupported: %d, maximumApfProgramSize: %d }", - mApfVersionSupported, mMaximumApfProgramSize)); - pw.println("Filter update status: " + (mIsRunning ? "RUNNING" : "PAUSED")); - pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED")); - pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW")); - pw.println("Minimum RDNSS lifetime: " + mMinRdnssLifetimeSec); - try { - pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress()); - } catch (UnknownHostException|NullPointerException e) {} - - if (mLastTimeInstalledProgram == 0) { - pw.println("No program installed."); - return; - } - pw.println("Program updates: " + mNumProgramUpdates); - pw.println(String.format( - "Last program length %d, installed %ds ago, lifetime %ds", - mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram, - mLastInstalledProgramMinLifetime)); - - pw.print("Denylisted Ethertypes:"); - for (int p : mEthTypeBlackList) { - pw.print(String.format(" %04x", p)); - } - pw.println(); - pw.println("RA filters:"); - pw.increaseIndent(); - for (Ra ra: mRas) { - pw.println(ra); - pw.increaseIndent(); - pw.println(String.format( - "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen)); - if (DBG) { - pw.println("Last match:"); - pw.increaseIndent(); - pw.println(ra.getLastMatchingPacket()); - pw.decreaseIndent(); - } - pw.decreaseIndent(); - } - pw.decreaseIndent(); - - pw.println("TCP Keepalive filters:"); - pw.increaseIndent(); - for (int i = 0; i < mKeepalivePackets.size(); ++i) { - final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); - if (keepalivePacket instanceof TcpKeepaliveAck) { - pw.print("Slot "); - pw.print(mKeepalivePackets.keyAt(i)); - pw.print(": "); - pw.println(keepalivePacket); - } - } - pw.decreaseIndent(); - - pw.println("NAT-T Keepalive filters:"); - pw.increaseIndent(); - for (int i = 0; i < mKeepalivePackets.size(); ++i) { - final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); - if (keepalivePacket instanceof NattKeepaliveResponse) { - pw.print("Slot "); - pw.print(mKeepalivePackets.keyAt(i)); - pw.print(": "); - pw.println(keepalivePacket); - } - } - pw.decreaseIndent(); - - if (DBG) { - pw.println("Last program:"); - pw.increaseIndent(); - pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */)); - pw.decreaseIndent(); - } - - pw.println("APF packet counters: "); - pw.increaseIndent(); - if (!hasDataAccess(mApfVersionSupported)) { - 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 = ApfCounterTracker.getCounterValue(mDataSnapshot, c); - // Only print non-zero counters - if (value != 0) { - pw.println(c.toString() + ": " + value); - } - - // If the counter's value decreases, it may have been cleaned up or there may be - // a bug. - if (value < mApfCounterTracker.getCounters().getOrDefault(c, 0L)) { - Log.e(TAG, "Error: Counter value unexpectedly decreased."); - } - } - } 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 - @VisibleForTesting - public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) { - return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength); - } - - private static int uint8(byte b) { - return b & 0xff; - } - - private static int getUint16(ByteBuffer buffer, int position) { - return buffer.getShort(position) & 0xffff; - } - - private static long getUint32(ByteBuffer buffer, int position) { - return Integer.toUnsignedLong(buffer.getInt(position)); - } - - private static int getUint8(ByteBuffer buffer, int position) { - return uint8(buffer.get(position)); - } - - private static int bytesToBEInt(byte[] bytes) { - return (uint8(bytes[0]) << 24) - + (uint8(bytes[1]) << 16) - + (uint8(bytes[2]) << 8) - + (uint8(bytes[3])); - } - - private static byte[] concatArrays(final byte[]... arr) { - int size = 0; - for (byte[] a : arr) { - size += a.length; - } - final byte[] result = new byte[size]; - int offset = 0; - for (byte[] a : arr) { - System.arraycopy(a, 0, result, offset, a.length); - offset += a.length; - } - return result; - } - - private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) { - if (mNetworkQuirkMetrics == null) return; - mNetworkQuirkMetrics.setEvent(event); - mNetworkQuirkMetrics.statsWrite(); - } - - /** - * Indicates whether the ApfFilter is currently running / paused for test and debugging - * purposes. - */ - public boolean isRunning() { - return mIsRunning; - } - - /** Pause ApfFilter updates for testing purposes. */ - public void pause() { - mIsRunning = false; - } - - /** Resume ApfFilter updates for testing purposes. */ - public void resume() { - mIsRunning = true; - } - - /** Return hex string of current APF snapshot for testing purposes. */ - public synchronized @Nullable String getDataSnapshotHexString() { - if (mDataSnapshot == null) { - return null; - } - return HexDump.toHexString(mDataSnapshot, 0, mDataSnapshot.length, false /* lowercase */); - } -}
diff --git a/src/android/net/apf/ProcfsParsingUtils.java b/src/android/net/apf/ProcfsParsingUtils.java index 4bac0f8..0d931a7 100644 --- a/src/android/net/apf/ProcfsParsingUtils.java +++ b/src/android/net/apf/ProcfsParsingUtils.java
@@ -15,18 +15,23 @@ */ package android.net.apf; +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL_HOST_MULTICAST; + import android.annotation.NonNull; import android.net.MacAddress; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.HexDump; +import com.android.net.module.util.HexDump; import java.io.BufferedReader; import java.io.IOException; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -39,6 +44,7 @@ private static final String IPV6_CONF_PATH = "/proc/sys/net/ipv6/conf/"; private static final String IPV6_ANYCAST_PATH = "/proc/net/anycast6"; private static final String ETHER_MCAST_PATH = "/proc/net/dev_mcast"; + private static final String IPV4_MCAST_PATH = "/proc/net/igmp"; private static final String IPV6_MCAST_PATH = "/proc/net/igmp6"; private ProcfsParsingUtils() { @@ -172,6 +178,75 @@ return addresses; } + + /** + * Parses IPv4 multicast addresses associated with a specific interface from a list of strings. + * + * @param lines A list of strings, each containing interface and IPv4 address information. + * @param ifname The name of the network interface for which to extract multicast addresses. + * @param endian The byte order of the address, almost always use native order. + * @return A list of Inet4Address objects representing the parsed IPv4 multicast addresses. + * If an error occurs during parsing, + * a list contains IPv4 all host (224.0.0.1) is returned. + */ + @VisibleForTesting + public static List<Inet4Address> parseIPv4MulticastAddresses( + @NonNull List<String> lines, @NonNull String ifname, @NonNull ByteOrder endian) { + final List<Inet4Address> ipAddresses = new ArrayList<>(); + + try { + String name = ""; + // parse output similar to `ip maddr` command (iproute2/ip/ipmaddr.c#read_igmp()) + for (String line : lines) { + final String[] parts = line.trim().split("\\s+"); + if (!line.startsWith("\t")) { + name = parts[1]; + if (name.endsWith(":")) { + name = name.substring(0, name.length() - 1); + } + continue; + } + + if (!name.equals(ifname)) { + continue; + } + + final String hexIp = parts[0]; + final byte[] ipArray = HexDump.hexStringToByteArray(hexIp); + final byte[] convertArray = + (endian == ByteOrder.LITTLE_ENDIAN) + ? convertIPv4BytesToBigEndian(ipArray) : ipArray; + final Inet4Address ipv4Address = + (Inet4Address) InetAddress.getByAddress(convertArray); + + ipAddresses.add(ipv4Address); + } + } catch (UnknownHostException | IllegalArgumentException e) { + Log.wtf(TAG, "failed to convert to Inet4Address.", e); + // always return IPv4 all host address (224.0.0.1) if any error during parsing. + // this aligns with kernel behavior, it will join 224.0.0.1 when the interface is up. + ipAddresses.clear(); + ipAddresses.add(IPV4_ADDR_ALL_HOST_MULTICAST); + } + + return ipAddresses; + } + + /** + * Converts an IPv4 address from little-endian byte order to big-endian byte order. + * + * @param bytes The IPv4 address in little-endian byte order. + * @return The IPv4 address in big-endian byte order. + */ + private static byte[] convertIPv4BytesToBigEndian(byte[] bytes) { + final ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.order(ByteOrder.LITTLE_ENDIAN); + final ByteBuffer bigEndianBuffer = ByteBuffer.allocate(4); + bigEndianBuffer.order(ByteOrder.BIG_ENDIAN); + bigEndianBuffer.putInt(buffer.getInt()); + return bigEndianBuffer.array(); + } + /** * Returns the traffic class for the specified interface. * The function loads the existing traffic class from the file @@ -228,4 +303,19 @@ final List<String> lines = readFile(IPV6_MCAST_PATH); return parseIPv6MulticastAddresses(lines, ifname); } + + /** + * The function loads the existing IPv4 multicast addresses from the file `/proc/net/igmp6`. + * If the file does not exist or the interface is not found, the function returns empty list. + * + * @param ifname The name of the network interface to query. + * @return A list of Inet4Address objects representing the IPv4 multicast addresses + * found for the interface. + * If the file cannot be read or there are no addresses, an empty list is returned. + */ + public static List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) { + final List<String> lines = readFile(IPV4_MCAST_PATH); + // follow the same pattern as NetlinkMonitor#handlePacket() for device's endian order + return parseIPv4MulticastAddresses(lines, ifname, ByteOrder.nativeOrder()); + } }
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 7447fc5..0efe7b2 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -120,7 +120,6 @@ import android.net.apf.AndroidPacketFilter; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; -import android.net.apf.LegacyApfFilter; import android.net.dhcp.DhcpClient; import android.net.dhcp.DhcpPacket; import android.net.dhcp6.Dhcp6Client; @@ -805,7 +804,6 @@ // Experiment flag read from device config. private final boolean mDhcp6PrefixDelegationEnabled; - private final boolean mUseNewApfFilter; private final boolean mIsAcceptRaMinLftEnabled; private final boolean mEnableApfPollingCounters; private final boolean mPopulateLinkAddressLifetime; @@ -986,15 +984,9 @@ */ public AndroidPacketFilter maybeCreateApfFilter(Handler handler, Context context, ApfFilter.ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics, - boolean useNewApfFilter) { - if (useNewApfFilter) { - return ApfFilter.maybeCreate(handler, context, config, ifParams, cb, - networkQuirkMetrics); - } else { - return LegacyApfFilter.maybeCreate(context, config, ifParams, cb, - networkQuirkMetrics); - } + IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics) { + return ApfFilter.maybeCreate(handler, context, config, ifParams, cb, + networkQuirkMetrics); } /** @@ -1070,8 +1062,6 @@ mApfCounterPollingIntervalMs = mDependencies.getDeviceConfigPropertyInt( CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS; - mUseNewApfFilter = SdkLevel.isAtLeastV() || mDependencies.isFeatureNotChickenedOut(context, - APF_NEW_RA_FILTER_VERSION); mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context, APF_POLLING_COUNTERS_VERSION); mIsAcceptRaMinLftEnabled = @@ -2643,7 +2633,7 @@ setIpv6Sysctl(ACCEPT_RA, 2); setIpv6Sysctl(ACCEPT_RA_DEFRTR, 1); maybeRestoreDadTransmits(); - if (mUseNewApfFilter && mIsAcceptRaMinLftEnabled + if (mIsAcceptRaMinLftEnabled && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) { setIpv6Sysctl(ACCEPT_RA_MIN_LFT, 0 /* sysctl default */); } @@ -2774,7 +2764,7 @@ // Check the feature flag first before reading IPv6 sysctl, which can prevent from // triggering a potential kernel bug about the sysctl. // TODO: add unit test to check if the setIpv6Sysctl() is called or not. - if (mIsAcceptRaMinLftEnabled && mUseNewApfFilter + if (mIsAcceptRaMinLftEnabled && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) { setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft); final Integer acceptRaMinLft = getIpv6Sysctl(ACCEPT_RA_MIN_LFT); @@ -2788,7 +2778,7 @@ apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs; apfConfig.hasClatInterface = mHasSeenClatInterface; return mDependencies.maybeCreateApfFilter(getHandler(), mContext, apfConfig, - mInterfaceParams, mCallback, mNetworkQuirkMetrics, mUseNewApfFilter); + mInterfaceParams, mCallback, mNetworkQuirkMetrics); } private boolean handleUpdateApfCapabilities(@NonNull final ApfCapabilities apfCapabilities) {
diff --git a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java index c2c51f6..3611c26 100644 --- a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java +++ b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
@@ -48,10 +48,8 @@ import static android.net.apf.ApfCounterTracker.Counter.DROPPED_RA; import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP; import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_BROADCAST_REPLY; -import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_NON_IPV4; import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST; import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNICAST_REPLY; -import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNKNOWN; import static android.net.apf.ApfCounterTracker.Counter.PASSED_DHCP; import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4; import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER; @@ -85,6 +83,7 @@ import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_MULTICAST; import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE; import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NON_DHCP4; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_TCP_PORT7_UNICAST; import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK; import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST; import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_NA; @@ -141,12 +140,8 @@ // The counter sequence should be keep the same in ApfCounterTracker.java Map.entry(PASSED_ARP, CN_PASSED_ARP), Map.entry(PASSED_ARP_BROADCAST_REPLY, CN_PASSED_ARP_BROADCAST_REPLY), - // deprecated in ApfFilter, PASSED_ARP_NON_IPV4 ==> DROPPED_ARP_NON_IPV4 - Map.entry(PASSED_ARP_NON_IPV4, CN_UNKNOWN), Map.entry(PASSED_ARP_REQUEST, CN_PASSED_ARP_REQUEST), Map.entry(PASSED_ARP_UNICAST_REPLY, CN_PASSED_ARP_UNICAST_REPLY), - // deprecated in ApfFilter, PASSED_ARP_UNKNOWN ==> DROPPED_ARP_UNKNOWN - Map.entry(PASSED_ARP_UNKNOWN, CN_UNKNOWN), Map.entry(PASSED_DHCP, CN_PASSED_DHCP), Map.entry(PASSED_IPV4, CN_PASSED_IPV4), Map.entry(PASSED_IPV4_FROM_DHCPV4_SERVER, CN_PASSED_IPV4_FROM_DHCPV4_SERVER), @@ -182,8 +177,7 @@ Map.entry(DROPPED_IPV6_KEEPALIVE_ACK, CN_DROPPED_IPV6_KEEPALIVE_ACK), Map.entry(DROPPED_IPV4_NATT_KEEPALIVE, CN_DROPPED_IPV4_NATT_KEEPALIVE), Map.entry(DROPPED_MDNS, CN_DROPPED_MDNS), - // TODO: Not supported yet in the metrics backend. - Map.entry(DROPPED_IPV4_TCP_PORT7_UNICAST, CN_UNKNOWN), + Map.entry(DROPPED_IPV4_TCP_PORT7_UNICAST, CN_DROPPED_IPV4_TCP_PORT7_UNICAST), Map.entry(DROPPED_ARP_NON_IPV4, CN_DROPPED_ARP_NON_IPV4), Map.entry(DROPPED_ARP_OTHER_HOST, CN_DROPPED_ARP_OTHER_HOST), Map.entry(DROPPED_ARP_REPLY_SPA_NO_HOST, CN_DROPPED_ARP_REPLY_SPA_NO_HOST),
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto index 43bc414..972e1f1 100644 --- a/src/com/android/networkstack/metrics/stats.proto +++ b/src/com/android/networkstack/metrics/stats.proto
@@ -239,8 +239,7 @@ /** * Logs APF session information event. * Logged from: - * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java or - * packages/modules/NetworkStack/src/android/net/apf/LegacyApfFilter.java + * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java */ message ApfSessionInfoReported { // The version of APF, where version = -1 equals APF disable.
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index fce06e4..30d1228 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -323,6 +323,21 @@ } /** + * Convert IPv4 multicast address to ethernet multicast address in network order. + */ + public static MacAddress ipv4MulticastToEthernetMulticast(@NonNull final Inet4Address addr) { + final byte[] etherMulticast = new byte[6]; + final byte[] ipv4Multicast = addr.getAddress(); + etherMulticast[0] = (byte) 0x01; + etherMulticast[1] = (byte) 0x00; + etherMulticast[2] = (byte) 0x5e; + etherMulticast[3] = (byte) (ipv4Multicast[1] & 0x7f); + etherMulticast[4] = ipv4Multicast[2]; + etherMulticast[5] = ipv4Multicast[3]; + return MacAddress.fromBytes(etherMulticast); + } + + /** * Convert IPv6 multicast address to ethernet multicast address in network order. */ public static MacAddress ipv6MulticastToEthernetMulticast(@NonNull final Inet6Address addr) {
diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt index 29e6237..fc0bb34 100644 --- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt +++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -62,6 +62,7 @@ import java.io.FileDescriptor import java.net.Inet4Address import java.net.Inet6Address +import java.net.InetAddress import java.nio.ByteBuffer import java.util.Arrays import kotlin.reflect.KClass @@ -346,6 +347,30 @@ fun testGenericDhcpResponseWithMfBitDropped() { doTestDhcpResponseWithMfBitDropped(true) } + + @Test + fun testConvertIpv4AddressToEthernetMulticast() { + var mcastAddrs = listOf( + // ipv4 multicast address, multicast ethernet address + Pair( + InetAddress.getByName("224.0.0.1") as Inet4Address, + MacAddress.fromString("01:00:5e:00:00:01") + ), + Pair( + InetAddress.getByName("239.128.1.1") as Inet4Address, + MacAddress.fromString("01:00:5e:00:01:01") + ), + Pair( + InetAddress.getByName("239.255.255.255") as Inet4Address, + MacAddress.fromString("01:00:5e:7f:ff:ff") + ) + ) + + for ((addr, expectAddr) in mcastAddrs) { + val ether = NetworkStackUtils.ipv4MulticastToEthernetMulticast(addr) + assertEquals(expectAddr, ether) + } + } } private fun ByteBuffer.readAsArray(): ByteArray {
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt index 1d821ef..0645cdd 100644 --- a/tests/unit/src/android/net/apf/ApfFilterTest.kt +++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -98,6 +98,7 @@ import com.android.testutils.quitResources import com.android.testutils.waitForIdle import java.io.FileDescriptor +import java.net.Inet4Address import java.net.Inet6Address import java.net.InetAddress import kotlin.test.assertContentEquals @@ -2140,4 +2141,29 @@ val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) assertContentEquals(ByteArray(4096) { 0 }, program) } + + @Test + fun testApfIPv4MulticastAddrsUpdate() { + val apfFilter = getApfFilter() + // mock IPv4 multicast address from /proc/net/igmp + val mcastAddrs = mutableListOf( + InetAddress.getByName("224.0.0.1") as Inet4Address + ) + consumeInstalledProgram(ipClientCallback, installCnt = 2) + + doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) + apfFilter.updateIPv4MulticastAddrs() + consumeInstalledProgram(ipClientCallback, installCnt = 1) + assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses) + + val addr = InetAddress.getByName("239.0.0.1") as Inet4Address + mcastAddrs.add(addr) + doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) + apfFilter.updateIPv4MulticastAddrs() + consumeInstalledProgram(ipClientCallback, installCnt = 1) + assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses) + + apfFilter.updateIPv4MulticastAddrs() + verify(ipClientCallback, never()).installPacketFilter(any()) + } }
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index 9a4a224..ac5b6b2 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -57,7 +57,6 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -2833,21 +2832,6 @@ } @Test - public void testGenerateApfProgramException() { - final ApfConfiguration config = getDefaultConfig(); - ApfFilter apfFilter = getApfFilter(config); - // Simulate exception during installNewProgram() by mocking - // mDependencies.elapsedRealtime() to throw an exception (this method doesn't throw in - // real-world scenarios). - doThrow(new IllegalStateException("test exception")).when(mDependencies).elapsedRealtime(); - synchronized (apfFilter) { - apfFilter.installNewProgram(); - } - verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); - verify(mNetworkQuirkMetrics).statsWrite(); - } - - @Test public void testApfSessionInfoMetrics() throws Exception { final ApfConfiguration config = getDefaultConfig(); config.apfVersionSupported = 4;
diff --git a/tests/unit/src/android/net/apf/LegacyApfTest.java b/tests/unit/src/android/net/apf/LegacyApfTest.java deleted file mode 100644 index 319a997..0000000 --- a/tests/unit/src/android/net/apf/LegacyApfTest.java +++ /dev/null
@@ -1,2045 +0,0 @@ -/* - * Copyright (C) 2012 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.apf; - -import static android.net.apf.ApfJniUtils.dropsAllPackets; -import static android.net.apf.ApfTestHelpers.TIMEOUT_MS; -import static android.system.OsConstants.AF_UNIX; -import static android.net.apf.ApfTestHelpers.DROP; -import static android.net.apf.ApfTestHelpers.PASS; -import static android.system.OsConstants.ETH_P_ARP; -import static android.system.OsConstants.ETH_P_IP; -import static android.system.OsConstants.ETH_P_IPV6; -import static android.system.OsConstants.IPPROTO_ICMPV6; -import static android.system.OsConstants.IPPROTO_TCP; -import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.SOCK_STREAM; - -import static com.android.net.module.util.HexDump.hexStringToByteArray; -import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.NattKeepalivePacketDataParcelable; -import android.net.TcpKeepalivePacketDataParcelable; -import android.net.apf.ApfCounterTracker.Counter; -import android.net.apf.ApfFilter.ApfConfiguration; -import android.net.ip.IIpClientCallbacks; -import android.net.ip.IpClient; -import android.net.metrics.IpConnectivityLog; -import android.os.Build; -import android.os.ConditionVariable; -import android.os.PowerManager; -import android.stats.connectivity.NetworkQuirkEvent; -import android.system.ErrnoException; -import android.system.Os; -import android.text.format.DateUtils; -import android.util.ArrayMap; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.HexDump; -import com.android.net.module.util.InterfaceParams; -import com.android.net.module.util.NetworkStackConstants; -import com.android.net.module.util.SharedLog; -import com.android.networkstack.apishim.NetworkInformationShimImpl; -import com.android.networkstack.metrics.ApfSessionInfoMetrics; -import com.android.networkstack.metrics.IpClientRaInfoMetrics; -import com.android.networkstack.metrics.NetworkQuirkMetrics; -import com.android.server.networkstack.tests.R; -import com.android.testutils.ConcurrentUtils; -import com.android.testutils.DevSdkIgnoreRule; -import com.android.testutils.DevSdkIgnoreRunner; - -import libcore.io.IoUtils; -import libcore.io.Streams; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Random; - -/** - * Tests for APF program generator and interpreter. - * - * The test cases will be executed by both APFv4 and APFv6 interpreter. - */ -@DevSdkIgnoreRunner.MonitorThreadLeak -@RunWith(DevSdkIgnoreRunner.class) -@SmallTest -public class LegacyApfTest { - private static final int APF_VERSION_2 = 2; - - @Rule - public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); - // Indicates which apf interpreter to run. - @Parameterized.Parameter() - public int mApfVersion; - - @Parameterized.Parameters - public static Iterable<? extends Object> data() { - return Arrays.asList(4, 6); - } - - @Mock private Context mContext; - @Mock - private ApfFilter.Dependencies mDependencies; - @Mock private PowerManager mPowerManager; - @Mock private IpConnectivityLog mIpConnectivityLog; - @Mock private NetworkQuirkMetrics mNetworkQuirkMetrics; - @Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics; - @Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics; - @Mock private LegacyApfFilter.Clock mClock; - @GuardedBy("mApfFilterCreated") - private final ArrayList<AndroidPacketFilter> mApfFilterCreated = new ArrayList<>(); - @GuardedBy("mThreadsToBeCleared") - private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>(); - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); - doReturn(mApfSessionInfoMetrics).when(mDependencies).getApfSessionInfoMetrics(); - doReturn(mIpClientRaInfoMetrics).when(mDependencies).getIpClientRaInfoMetrics(); - doAnswer((invocation) -> { - synchronized (mApfFilterCreated) { - mApfFilterCreated.add(invocation.getArgument(0)); - } - return null; - }).when(mDependencies).onApfFilterCreated(any()); - doAnswer((invocation) -> { - synchronized (mThreadsToBeCleared) { - mThreadsToBeCleared.add(invocation.getArgument(0)); - } - return null; - }).when(mDependencies).onThreadCreated(any()); - } - - private void quitThreads() throws Exception { - ConcurrentUtils.quitThreads( - THREAD_QUIT_MAX_RETRY_COUNT, - false /* interrupt */, - HANDLER_TIMEOUT_MS, - () -> { - synchronized (mThreadsToBeCleared) { - final ArrayList<Thread> ret = new ArrayList<>(mThreadsToBeCleared); - mThreadsToBeCleared.clear(); - return ret; - } - }); - } - - private void shutdownApfFilters() throws Exception { - ConcurrentUtils.quitResources(THREAD_QUIT_MAX_RETRY_COUNT, () -> { - synchronized (mApfFilterCreated) { - final ArrayList<AndroidPacketFilter> ret = - new ArrayList<>(mApfFilterCreated); - mApfFilterCreated.clear(); - return ret; - } - }, (apf) -> { - apf.shutdown(); - }); - synchronized (mApfFilterCreated) { - assertEquals("ApfFilters did not fully shutdown.", - 0, mApfFilterCreated.size()); - } - // It's necessary to wait until all ReceiveThreads have finished running because - // clearInlineMocks clears all Mock objects, including some privilege frameworks - // required by logStats, at the end of ReceiveThread#run. - quitThreads(); - } - - @After - public void tearDown() throws Exception { - shutdownApfFilters(); - // Clear mocks to prevent from stubs holding instances and cause memory leaks. - Mockito.framework().clearInlineMocks(); - } - - private static final String TAG = "ApfTest"; - - private static final boolean DROP_MULTICAST = true; - private static final boolean ALLOW_MULTICAST = false; - - private static final boolean DROP_802_3_FRAMES = true; - private static final boolean ALLOW_802_3_FRAMES = false; - - private static final int MIN_RDNSS_LIFETIME_SEC = 0; - private static final int MIN_METRICS_SESSION_DURATIONS_MS = 300_000; - - private static final int HANDLER_TIMEOUT_MS = 1000; - private static final int THREAD_QUIT_MAX_RETRY_COUNT = 3; - - // Constants for opcode encoding - private static final byte LI_OP = (byte)(13 << 3); - private static final byte LDDW_OP = (byte)(22 << 3); - private static final byte STDW_OP = (byte)(23 << 3); - private static final byte SIZE0 = (byte)(0 << 1); - private static final byte SIZE8 = (byte)(1 << 1); - private static final byte SIZE16 = (byte)(2 << 1); - private static final byte SIZE32 = (byte)(3 << 1); - private static final byte R1_REG = 1; - - private static ApfConfiguration getDefaultConfig() { - ApfFilter.ApfConfiguration config = new ApfConfiguration(); - config.apfVersionSupported = 2; - config.apfRamSize = 4096; - config.multicastFilter = ALLOW_MULTICAST; - config.ieee802_3Filter = ALLOW_802_3_FRAMES; - config.ethTypeBlackList = new int[0]; - config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC; - config.minRdnssLifetimeSec = 67; - config.minMetricsSessionDurationMs = MIN_METRICS_SESSION_DURATIONS_MS; - return config; - } - - private void assertPass(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException { - ApfTestHelpers.assertPass(mApfVersion, gen); - } - - private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException { - ApfTestHelpers.assertDrop(mApfVersion, gen); - } - - private void assertPass(byte[] program, byte[] packet) { - ApfTestHelpers.assertPass(mApfVersion, program, packet); - } - - private void assertDrop(byte[] program, byte[] packet) { - ApfTestHelpers.assertDrop(mApfVersion, program, packet); - } - - private void assertPass(byte[] program, byte[] packet, int filterAge) { - ApfTestHelpers.assertPass(mApfVersion, program, packet, filterAge); - } - - private void assertDrop(byte[] program, byte[] packet, int filterAge) { - ApfTestHelpers.assertDrop(mApfVersion, program, packet, filterAge); - } - - private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge) - throws ApfV4Generator.IllegalInstructionException { - ApfTestHelpers.assertPass(mApfVersion, gen, packet, filterAge); - } - - private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge) - throws ApfV4Generator.IllegalInstructionException { - ApfTestHelpers.assertDrop(mApfVersion, gen, packet, filterAge); - } - - private void assertDataMemoryContents(int expected, byte[] program, byte[] packet, - byte[] data, byte[] expectedData) throws Exception { - ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data, - expectedData, false /* ignoreInterpreterVersion */); - } - - private void assertDataMemoryContentsIgnoreVersion(int expected, byte[] program, - byte[] packet, byte[] data, byte[] expectedData) throws Exception { - ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data, - expectedData, true /* ignoreInterpreterVersion */); - } - - private void assertVerdict(String msg, int expected, byte[] program, - byte[] packet, int filterAge) { - ApfTestHelpers.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge); - } - - private void assertVerdict(int expected, byte[] program, byte[] packet) { - ApfTestHelpers.assertVerdict(mApfVersion, expected, program, packet); - } - - /** - * Generate APF program, run pcap file though APF filter, then check all the packets in the file - * should be dropped. - */ - @Test - public void testApfFilterPcapFile() throws Exception { - final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151}; - String pcapFilename = stageFile(R.raw.apfPcap); - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16); - LinkProperties lp = new LinkProperties(); - lp.addLinkAddress(link); - - ApfConfiguration config = getDefaultConfig(); - config.apfVersionSupported = 4; - config.apfRamSize = 1700; - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - apfFilter.setLinkProperties(lp); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - byte[] data = new byte[Counter.totalSize()]; - final boolean result; - - result = dropsAllPackets(mApfVersion, program, data, pcapFilename); - Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false)); - - assertTrue("Failed to drop all packets by filter. \nAPF counters:" + - HexDump.toHexString(data, false), result); - } - - private static final int ETH_HEADER_LEN = 14; - private static final int ETH_DEST_ADDR_OFFSET = 0; - private static final int ETH_ETHERTYPE_OFFSET = 12; - private static final byte[] ETH_BROADCAST_MAC_ADDRESS = - {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; - private static final byte[] ETH_MULTICAST_MDNS_v4_MAC_ADDRESS = - {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; - private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS = - {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb}; - - private static final int IP_HEADER_OFFSET = ETH_HEADER_LEN; - - private static final int IPV4_HEADER_LEN = 20; - private static final int IPV4_TOTAL_LENGTH_OFFSET = IP_HEADER_OFFSET + 2; - private static final int IPV4_PROTOCOL_OFFSET = IP_HEADER_OFFSET + 9; - private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; - private static final int IPV4_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 16; - - private static final int IPV4_TCP_HEADER_LEN = 20; - private static final int IPV4_TCP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN; - private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0; - private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2; - private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4; - private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8; - private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12; - private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13; - - private static final int IPV4_UDP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN; - private static final int IPV4_UDP_SRC_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 0; - private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2; - private static final int IPV4_UDP_LENGTH_OFFSET = IPV4_UDP_HEADER_OFFSET + 4; - private static final int IPV4_UDP_PAYLOAD_OFFSET = IPV4_UDP_HEADER_OFFSET + 8; - private static final byte[] IPV4_BROADCAST_ADDRESS = - {(byte) 255, (byte) 255, (byte) 255, (byte) 255}; - - private static final int IPV6_HEADER_LEN = 40; - private static final int IPV6_PAYLOAD_LENGTH_OFFSET = IP_HEADER_OFFSET + 4; - private static final int IPV6_NEXT_HEADER_OFFSET = IP_HEADER_OFFSET + 6; - private static final int IPV6_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 8; - private static final int IPV6_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 24; - private static final int IPV6_PAYLOAD_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN; - private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 0; - private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2; - private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 4; - private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 8; - // The IPv6 all nodes address ff02::1 - private static final byte[] IPV6_ALL_NODES_ADDRESS = - { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; - private static final byte[] IPV6_ALL_ROUTERS_ADDRESS = - { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }; - private static final byte[] IPV6_SOLICITED_NODE_MULTICAST_ADDRESS = { - (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - (byte) 0xff, (byte) 0xab, (byte) 0xcd, (byte) 0xef, - }; - - private static final int ICMP6_TYPE_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN; - private static final int ICMP6_ROUTER_SOLICITATION = 133; - private static final int ICMP6_ROUTER_ADVERTISEMENT = 134; - private static final int ICMP6_NEIGHBOR_SOLICITATION = 135; - private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136; - - private static final int ICMP6_RA_HEADER_LEN = 16; - private static final int ICMP6_RA_CHECKSUM_OFFSET = - IP_HEADER_OFFSET + IPV6_HEADER_LEN + 2; - private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET = - IP_HEADER_OFFSET + IPV6_HEADER_LEN + 6; - private static final int ICMP6_RA_REACHABLE_TIME_OFFSET = - IP_HEADER_OFFSET + IPV6_HEADER_LEN + 8; - private static final int ICMP6_RA_RETRANSMISSION_TIMER_OFFSET = - IP_HEADER_OFFSET + IPV6_HEADER_LEN + 12; - private static final int ICMP6_RA_OPTION_OFFSET = - IP_HEADER_OFFSET + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN; - - private static final int ICMP6_PREFIX_OPTION_TYPE = 3; - private static final int ICMP6_PREFIX_OPTION_LEN = 32; - private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; - private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8; - - // From RFC6106: Recursive DNS Server option - private static final int ICMP6_RDNSS_OPTION_TYPE = 25; - // From RFC6106: DNS Search List option - private static final int ICMP6_DNSSL_OPTION_TYPE = 31; - - // From RFC4191: Route Information option - private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; - // Above three options all have the same format: - private static final int ICMP6_4_BYTE_OPTION_LEN = 8; - private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4; - private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; - - private static final int UDP_HEADER_LEN = 8; - private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22; - - 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 ARP_HEADER_OFFSET = ETH_HEADER_LEN; - private static final byte[] ARP_IPV4_REQUEST_HEADER = { - 0, 1, // Hardware type: Ethernet (1) - 8, 0, // Protocol type: IP (0x0800) - 6, // Hardware size: 6 - 4, // Protocol size: 4 - 0, 1 // Opcode: request (1) - }; - private static final byte[] ARP_IPV4_REPLY_HEADER = { - 0, 1, // Hardware type: Ethernet (1) - 8, 0, // Protocol type: IP (0x0800) - 6, // Hardware size: 6 - 4, // Protocol size: 4 - 0, 2 // Opcode: reply (2) - }; - private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14; - private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24; - - private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; - private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19 - private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1}; - private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2}; - private static final byte[] IPV4_SOURCE_ADDR = {10, 0, 0, 3}; - private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1}; - private static final byte[] BUG_PROBE_SOURCE_ADDR1 = {0, 0, 1, 2}; - private static final byte[] BUG_PROBE_SOURCE_ADDR2 = {3, 4, 0, 0}; - private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0}; - private static final byte[] IPV4_MDNS_MULTICAST_ADDR = {(byte) 224, 0, 0, (byte) 251}; - private static final byte[] IPV6_MDNS_MULTICAST_ADDR = - {(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfb}; - private static final int IPV6_UDP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2; - private static final int MDNS_UDP_PORT = 5353; - - private static void setIpv4VersionFields(ByteBuffer packet) { - packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP); - packet.put(IP_HEADER_OFFSET, (byte) 0x45); - } - - private static void setIpv6VersionFields(ByteBuffer packet) { - packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); - packet.put(IP_HEADER_OFFSET, (byte) 0x60); - } - - private static ByteBuffer makeIpv4Packet(int proto) { - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - setIpv4VersionFields(packet); - packet.put(IPV4_PROTOCOL_OFFSET, (byte) proto); - return packet; - } - - private static ByteBuffer makeIpv6Packet(int nextHeader) { - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - setIpv6VersionFields(packet); - packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) nextHeader); - return packet; - } - - @Test - public void testApfFilterIPv4() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); - LinkProperties lp = new LinkProperties(); - lp.addLinkAddress(link); - - ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - apfFilter.setLinkProperties(lp); - - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - // Verify empty packet of 100 zero bytes is passed - assertPass(program, packet.array()); - - // Verify unicast IPv4 packet is passed - put(packet, ETH_DEST_ADDR_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR); - packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); - put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR); - assertPass(program, packet.array()); - - // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088) - put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS); - assertDrop(program, packet.array()); - put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR); - assertDrop(program, packet.array()); - - // Verify multicast/broadcast IPv4, not DHCP to us, is dropped - put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS); - assertDrop(program, packet.array()); - packet.put(IP_HEADER_OFFSET, (byte) 0x45); - assertDrop(program, packet.array()); - packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP); - assertDrop(program, packet.array()); - packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT); - assertDrop(program, packet.array()); - put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR); - assertDrop(program, packet.array()); - put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR); - assertDrop(program, packet.array()); - put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS); - assertDrop(program, packet.array()); - - // Verify broadcast IPv4 DHCP to us is passed - put(packet, DHCP_CLIENT_MAC_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR); - assertPass(program, packet.array()); - - // Verify unicast IPv4 DHCP to us is passed - put(packet, ETH_DEST_ADDR_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR); - assertPass(program, packet.array()); - } - - @Test - public void testApfFilterIPv6() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - ApfConfiguration config = getDefaultConfig(); - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - // Verify empty IPv6 packet is passed - ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP); - assertPass(program, packet.array()); - - // Verify empty ICMPv6 packet is passed - packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6); - assertPass(program, packet.array()); - - // Verify empty ICMPv6 NA packet is passed - packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT); - assertPass(program, packet.array()); - - // Verify ICMPv6 NA to ff02::1 is dropped - put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS); - assertDrop(program, packet.array()); - - // Verify ICMPv6 NA to ff02::2 is dropped - put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS); - assertDrop(program, packet.array()); - - // Verify ICMPv6 NA to Solicited-Node Multicast is passed - put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_SOLICITED_NODE_MULTICAST_ADDRESS); - assertPass(program, packet.array()); - - // Verify ICMPv6 RS to any is dropped - packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION); - assertDrop(program, packet.array()); - put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS); - assertDrop(program, packet.array()); - } - - @Test - public void testApfFilterMulticast() throws Exception { - final byte[] unicastIpv4Addr = {(byte)192,0,2,63}; - final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255}; - final byte[] multicastIpv4Addr = {(byte)224,0,0,1}; - final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb}; - - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24); - LinkProperties lp = new LinkProperties(); - lp.addLinkAddress(link); - - ApfConfiguration config = getDefaultConfig(); - config.ieee802_3Filter = DROP_802_3_FRAMES; - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - apfFilter.setLinkProperties(lp); - - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - // Construct IPv4 and IPv6 multicast packets. - ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP); - put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr); - - ByteBuffer mcastv6packet = makeIpv6Packet(IPPROTO_UDP); - put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr); - - // Construct IPv4 broadcast packet. - ByteBuffer bcastv4packet1 = makeIpv4Packet(IPPROTO_UDP); - bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS); - bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); - put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr); - - ByteBuffer bcastv4packet2 = makeIpv4Packet(IPPROTO_UDP); - bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS); - bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); - put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS); - - // Construct IPv4 broadcast with L2 unicast address packet (b/30231088). - ByteBuffer bcastv4unicastl2packet = makeIpv4Packet(IPPROTO_UDP); - bcastv4unicastl2packet.put(TestLegacyApfFilter.MOCK_MAC_ADDR); - bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); - put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr); - - // Verify initially disabled multicast filter is off - assertPass(program, mcastv4packet.array()); - assertPass(program, mcastv6packet.array()); - assertPass(program, bcastv4packet1.array()); - assertPass(program, bcastv4packet2.array()); - assertPass(program, bcastv4unicastl2packet.array()); - - // Turn on multicast filter and verify it works - ipClientCallback.resetApfProgramWait(); - apfFilter.setMulticastFilter(true); - program = ipClientCallback.assertProgramUpdateAndGet(); - assertDrop(program, mcastv4packet.array()); - assertDrop(program, mcastv6packet.array()); - assertDrop(program, bcastv4packet1.array()); - assertDrop(program, bcastv4packet2.array()); - assertDrop(program, bcastv4unicastl2packet.array()); - - // Turn off multicast filter and verify it's off - ipClientCallback.resetApfProgramWait(); - apfFilter.setMulticastFilter(false); - program = ipClientCallback.assertProgramUpdateAndGet(); - assertPass(program, mcastv4packet.array()); - assertPass(program, mcastv6packet.array()); - assertPass(program, bcastv4packet1.array()); - assertPass(program, bcastv4packet2.array()); - assertPass(program, bcastv4unicastl2packet.array()); - - // Verify it can be initialized to on - ipClientCallback.resetApfProgramWait(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - apfFilter.setLinkProperties(lp); - program = ipClientCallback.assertProgramUpdateAndGet(); - assertDrop(program, mcastv4packet.array()); - assertDrop(program, mcastv6packet.array()); - assertDrop(program, bcastv4packet1.array()); - assertDrop(program, bcastv4unicastl2packet.array()); - - // Verify that ICMPv6 multicast is not dropped. - mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6); - assertPass(program, mcastv6packet.array()); - } - - @Test - public void testApfFilterMulticastPingWhileDozing() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration configuration = getDefaultConfig(); - final LegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, configuration, - ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - - // Construct a multicast ICMPv6 ECHO request. - final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb}; - ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6); - packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE); - put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr); - - // Normally, we let multicast pings alone... - assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); - - // ...and even while dozing... - apfFilter.setDozeMode(true); - assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); - - // ...but when the multicast filter is also enabled, drop the multicast pings to save power. - apfFilter.setMulticastFilter(true); - assertDrop(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); - - // However, we should still let through all other ICMPv6 types. - ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone()); - setIpv6VersionFields(packet); - packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6); - raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT); - assertPass(ipClientCallback.assertProgramUpdateAndGet(), raPacket.array()); - - // Now wake up from doze mode to ensure that we no longer drop the packets. - // (The multicast filter is still enabled at this point). - apfFilter.setDozeMode(false); - assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); - - apfFilter.shutdown(); - } - - @Test - @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - public void testApfFilter802_3() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - ApfConfiguration config = getDefaultConfig(); - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - // Verify empty packet of 100 zero bytes is passed - // Note that eth-type = 0 makes it an IEEE802.3 frame - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - assertPass(program, packet.array()); - - // Verify empty packet with IPv4 is passed - setIpv4VersionFields(packet); - assertPass(program, packet.array()); - - // Verify empty IPv6 packet is passed - setIpv6VersionFields(packet); - assertPass(program, packet.array()); - - // Now turn on the filter - ipClientCallback.resetApfProgramWait(); - config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - program = ipClientCallback.assertProgramUpdateAndGet(); - - // Verify that IEEE802.3 frame is dropped - // In this case ethtype is used for payload length - packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14)); - assertDrop(program, packet.array()); - - // Verify that IPv4 (as example of Ethernet II) frame will pass - setIpv4VersionFields(packet); - assertPass(program, packet.array()); - - // Verify that IPv6 (as example of Ethernet II) frame will pass - setIpv6VersionFields(packet); - assertPass(program, packet.array()); - } - - @Test - @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - public void testApfFilterEthTypeBL() throws Exception { - final int[] emptyBlackList = {}; - final int[] ipv4BlackList = {ETH_P_IP}; - final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6}; - - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - ApfConfiguration config = getDefaultConfig(); - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - // Verify empty packet of 100 zero bytes is passed - // Note that eth-type = 0 makes it an IEEE802.3 frame - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - assertPass(program, packet.array()); - - // Verify empty packet with IPv4 is passed - setIpv4VersionFields(packet); - assertPass(program, packet.array()); - - // Verify empty IPv6 packet is passed - setIpv6VersionFields(packet); - assertPass(program, packet.array()); - - // Now add IPv4 to the black list - ipClientCallback.resetApfProgramWait(); - config.ethTypeBlackList = ipv4BlackList; - apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - program = ipClientCallback.assertProgramUpdateAndGet(); - - // Verify that IPv4 frame will be dropped - setIpv4VersionFields(packet); - assertDrop(program, packet.array()); - - // Verify that IPv6 frame will pass - setIpv6VersionFields(packet); - assertPass(program, packet.array()); - - // Now let us have both IPv4 and IPv6 in the black list - ipClientCallback.resetApfProgramWait(); - config.ethTypeBlackList = ipv4Ipv6BlackList; - apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - program = ipClientCallback.assertProgramUpdateAndGet(); - - // Verify that IPv4 frame will be dropped - setIpv4VersionFields(packet); - assertDrop(program, packet.array()); - - // Verify that IPv6 frame will be dropped - setIpv6VersionFields(packet); - assertDrop(program, packet.array()); - } - - private byte[] getProgram(MockIpClientCallback cb, TestLegacyApfFilter filter, - LinkProperties lp) { - cb.resetApfProgramWait(); - filter.setLinkProperties(lp); - return cb.assertProgramUpdateAndGet(); - } - - private void verifyArpFilter(byte[] program, int filterResult) { - // Verify ARP request packet - assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR)); - assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR)); - assertVerdict(DROP, program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR)); - - // Verify ARP reply packets from different source ip - assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR)); - assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR)); - assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR)); - assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR)); - - // Verify unicast ARP reply packet is always accepted. - assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR)); - assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR)); - assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR)); - - // Verify GARP reply packets are always filtered - assertDrop(program, garpReply()); - } - - @Test - public void testApfFilterArp() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - - // Verify initially ARP request filter is off, and GARP filter is on. - verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS); - - // Inform ApfFilter of our address and verify ARP filtering is on - LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24); - LinkProperties lp = new LinkProperties(); - assertTrue(lp.addLinkAddress(linkAddress)); - verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP); - - // Inform ApfFilter of loss of IP and verify ARP filtering is off - verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS); - } - - private static byte[] arpReply(byte[] sip, byte[] tip) { - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP); - put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER); - put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip); - put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip); - return packet.array(); - } - - private static byte[] arpRequestBroadcast(byte[] tip) { - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP); - put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS); - put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER); - put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip); - return packet.array(); - } - - private static byte[] garpReply() { - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP); - put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS); - put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER); - put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR); - return packet.array(); - } - - private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 5}; - private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 6}; - private static final byte[] IPV4_ANOTHER_ADDR = {10, 0 , 0, 7}; - private static final byte[] IPV6_KEEPALIVE_SRC_ADDR = - {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf1}; - private static final byte[] IPV6_KEEPALIVE_DST_ADDR = - {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf2}; - private static final byte[] IPV6_ANOTHER_ADDR = - {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf5}; - - @Test - public void testApfFilterKeepaliveAck() throws Exception { - final MockIpClientCallback cb = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, cb, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - byte[] program; - final int srcPort = 12345; - final int dstPort = 54321; - final int seqNum = 2123456789; - final int ackNum = 1234567890; - final int anotherSrcPort = 23456; - final int anotherDstPort = 65432; - final int anotherSeqNum = 2123456780; - final int anotherAckNum = 1123456789; - final int slot1 = 1; - final int slot2 = 2; - final int window = 14480; - final int windowScale = 4; - - // src: 10.0.0.5, port: 12345 - // dst: 10.0.0.6, port: 54321 - InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR); - InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR); - - final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable(); - parcel.srcAddress = srcAddr.getAddress(); - parcel.srcPort = srcPort; - parcel.dstAddress = dstAddr.getAddress(); - parcel.dstPort = dstPort; - parcel.seq = seqNum; - parcel.ack = ackNum; - - apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); - program = cb.assertProgramUpdateAndGet(); - - // Verify IPv4 keepalive ack packet is dropped - // src: 10.0.0.6, port: 54321 - // dst: 10.0.0.5, port: 12345 - assertDrop(program, - ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); - // Verify IPv4 non-keepalive ack packet from the same source address is passed - assertPass(program, - ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */)); - assertPass(program, - ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */)); - // Verify IPv4 packet from another address is passed - assertPass(program, - ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort, - anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); - - // Remove IPv4 keepalive filter - apfFilter.removeKeepalivePacketFilter(slot1); - - try { - // src: 2404:0:0:0:0:0:faf1, port: 12345 - // dst: 2404:0:0:0:0:0:faf2, port: 54321 - srcAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_SRC_ADDR); - dstAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_DST_ADDR); - - final TcpKeepalivePacketDataParcelable ipv6Parcel = - new TcpKeepalivePacketDataParcelable(); - ipv6Parcel.srcAddress = srcAddr.getAddress(); - ipv6Parcel.srcPort = srcPort; - ipv6Parcel.dstAddress = dstAddr.getAddress(); - ipv6Parcel.dstPort = dstPort; - ipv6Parcel.seq = seqNum; - ipv6Parcel.ack = ackNum; - - apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel); - program = cb.assertProgramUpdateAndGet(); - - // Verify IPv6 keepalive ack packet is dropped - // src: 2404:0:0:0:0:0:faf2, port: 54321 - // dst: 2404:0:0:0:0:0:faf1, port: 12345 - assertDrop(program, - ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum, seqNum + 1)); - // Verify IPv6 non-keepalive ack packet from the same source address is passed - assertPass(program, - ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum + 100, seqNum)); - // Verify IPv6 packet from another address is passed - assertPass(program, - ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort, - anotherDstPort, anotherSeqNum, anotherAckNum)); - - // Remove IPv6 keepalive filter - apfFilter.removeKeepalivePacketFilter(slot1); - - // Verify multiple filters - apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); - apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel); - program = cb.assertProgramUpdateAndGet(); - - // Verify IPv4 keepalive ack packet is dropped - // src: 10.0.0.6, port: 54321 - // dst: 10.0.0.5, port: 12345 - assertDrop(program, - ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); - // Verify IPv4 non-keepalive ack packet from the same source address is passed - assertPass(program, - ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */)); - // Verify IPv4 packet from another address is passed - assertPass(program, - ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort, - anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); - - // Verify IPv6 keepalive ack packet is dropped - // src: 2404:0:0:0:0:0:faf2, port: 54321 - // dst: 2404:0:0:0:0:0:faf1, port: 12345 - assertDrop(program, - ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum, seqNum + 1)); - // Verify IPv6 non-keepalive ack packet from the same source address is passed - assertPass(program, - ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum + 100, seqNum)); - // Verify IPv6 packet from another address is passed - assertPass(program, - ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort, - anotherDstPort, anotherSeqNum, anotherAckNum)); - - // Remove keepalive filters - apfFilter.removeKeepalivePacketFilter(slot1); - apfFilter.removeKeepalivePacketFilter(slot2); - } catch (UnsupportedOperationException e) { - // TODO: support V6 packets - } - - program = cb.assertProgramUpdateAndGet(); - - // Verify IPv4, IPv6 packets are passed - assertPass(program, - ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */)); - assertPass(program, - ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, ackNum, seqNum + 1)); - assertPass(program, - ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort, - dstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */)); - assertPass(program, - ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort, - dstPort, anotherSeqNum, anotherAckNum)); - } - - private static byte[] ipv4TcpPacket(byte[] sip, byte[] dip, int sport, - int dport, int seq, int ack, int dataLength) { - final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN; - - ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]); - - // Ethertype and IPv4 header - setIpv4VersionFields(packet); - packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); - packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_TCP); - put(packet, IPV4_SRC_ADDR_OFFSET, sip); - put(packet, IPV4_DEST_ADDR_OFFSET, dip); - packet.putShort(IPV4_TCP_SRC_PORT_OFFSET, (short) sport); - packet.putShort(IPV4_TCP_DEST_PORT_OFFSET, (short) dport); - packet.putInt(IPV4_TCP_SEQ_NUM_OFFSET, seq); - packet.putInt(IPV4_TCP_ACK_NUM_OFFSET, ack); - - // TCP header length 5(20 bytes), reserved 3 bits, NS=0 - packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50); - // TCP flags: ACK set - packet.put(IPV4_TCP_HEADER_FLAG_OFFSET, (byte) 0x10); - return packet.array(); - } - - private static byte[] ipv6TcpPacket(byte[] sip, byte[] tip, int sport, - int dport, int seq, int ack) { - ByteBuffer packet = ByteBuffer.wrap(new byte[100]); - setIpv6VersionFields(packet); - packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_TCP); - put(packet, IPV6_SRC_ADDR_OFFSET, sip); - put(packet, IPV6_DEST_ADDR_OFFSET, tip); - packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport); - packet.putShort(IPV6_TCP_DEST_PORT_OFFSET, (short) dport); - packet.putInt(IPV6_TCP_SEQ_NUM_OFFSET, seq); - packet.putInt(IPV6_TCP_ACK_NUM_OFFSET, ack); - return packet.array(); - } - - @Test - public void testApfFilterNattKeepalivePacket() throws Exception { - final MockIpClientCallback cb = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, cb, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - byte[] program; - final int srcPort = 1024; - final int dstPort = 4500; - final int slot1 = 1; - // NAT-T keepalive - final byte[] kaPayload = {(byte) 0xff}; - final byte[] nonKaPayload = {(byte) 0xfe}; - - // src: 10.0.0.5, port: 1024 - // dst: 10.0.0.6, port: 4500 - InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR); - InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR); - - final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable(); - parcel.srcAddress = srcAddr.getAddress(); - parcel.srcPort = srcPort; - parcel.dstAddress = dstAddr.getAddress(); - parcel.dstPort = dstPort; - - apfFilter.addNattKeepalivePacketFilter(slot1, parcel); - program = cb.assertProgramUpdateAndGet(); - - // Verify IPv4 keepalive packet is dropped - // src: 10.0.0.6, port: 4500 - // dst: 10.0.0.5, port: 1024 - byte[] pkt = ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, - IPV4_KEEPALIVE_SRC_ADDR, dstPort, srcPort, 1 /* dataLength */); - System.arraycopy(kaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, kaPayload.length); - assertDrop(program, pkt); - - // Verify a packet with payload length 1 byte but it is not 0xff will pass the filter. - System.arraycopy(nonKaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, nonKaPayload.length); - assertPass(program, pkt); - - // Verify IPv4 non-keepalive response packet from the same source address is passed - assertPass(program, - ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, 10 /* dataLength */)); - - // Verify IPv4 non-keepalive response packet from other source address is passed - assertPass(program, - ipv4UdpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, - dstPort, srcPort, 10 /* dataLength */)); - - apfFilter.removeKeepalivePacketFilter(slot1); - } - - private static byte[] ipv4UdpPacket(byte[] sip, byte[] dip, int sport, - int dport, int dataLength) { - final int totalLength = dataLength + IPV4_HEADER_LEN + UDP_HEADER_LEN; - final int udpLength = UDP_HEADER_LEN + dataLength; - ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]); - - // Ethertype and IPv4 header - setIpv4VersionFields(packet); - packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); - packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP); - put(packet, IPV4_SRC_ADDR_OFFSET, sip); - put(packet, IPV4_DEST_ADDR_OFFSET, dip); - packet.putShort(IPV4_UDP_SRC_PORT_OFFSET, (short) sport); - packet.putShort(IPV4_UDP_DEST_PORT_OFFSET, (short) dport); - packet.putShort(IPV4_UDP_LENGTH_OFFSET, (short) udpLength); - - return packet.array(); - } - - private static class RaPacketBuilder { - final ByteArrayOutputStream mPacket = new ByteArrayOutputStream(); - int mFlowLabel = 0x12345; - int mReachableTime = 30_000; - int mRetransmissionTimer = 1000; - - public RaPacketBuilder(int routerLft) throws Exception { - InetAddress src = InetAddress.getByName("fe80::1234:abcd"); - ByteBuffer buffer = ByteBuffer.allocate(ICMP6_RA_OPTION_OFFSET); - - buffer.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6); - buffer.position(ETH_HEADER_LEN); - - // skip version, tclass, flowlabel; set in build() - buffer.position(buffer.position() + 4); - - buffer.putShort((short) 0); // Payload length; updated later - buffer.put((byte) IPPROTO_ICMPV6); // Next header - buffer.put((byte) 0xff); // Hop limit - buffer.put(src.getAddress()); // Source address - buffer.put(IPV6_ALL_NODES_ADDRESS); // Destination address - - buffer.put((byte) ICMP6_ROUTER_ADVERTISEMENT); // Type - buffer.put((byte) 0); // Code (0) - buffer.putShort((short) 0); // Checksum (ignored) - buffer.put((byte) 64); // Hop limit - buffer.put((byte) 0); // M/O, reserved - buffer.putShort((short) routerLft); // Router lifetime - // skip reachable time; set in build() - // skip retransmission timer; set in build(); - - mPacket.write(buffer.array(), 0, buffer.capacity()); - } - - public RaPacketBuilder setFlowLabel(int flowLabel) { - mFlowLabel = flowLabel; - return this; - } - - public RaPacketBuilder setReachableTime(int reachable) { - mReachableTime = reachable; - return this; - } - - public RaPacketBuilder setRetransmissionTimer(int retrans) { - mRetransmissionTimer = retrans; - return this; - } - - public RaPacketBuilder addPioOption(int valid, int preferred, String prefixString) - throws Exception { - ByteBuffer buffer = ByteBuffer.allocate(ICMP6_PREFIX_OPTION_LEN); - - IpPrefix prefix = new IpPrefix(prefixString); - buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); // Type - buffer.put((byte) 4); // Length in 8-byte units - buffer.put((byte) prefix.getPrefixLength()); // Prefix length - buffer.put((byte) 0b11000000); // L = 1, A = 1 - buffer.putInt(valid); - buffer.putInt(preferred); - buffer.putInt(0); // Reserved - buffer.put(prefix.getRawAddress()); - - mPacket.write(buffer.array(), 0, buffer.capacity()); - return this; - } - - public RaPacketBuilder addRioOption(int lifetime, String prefixString) throws Exception { - IpPrefix prefix = new IpPrefix(prefixString); - - int optionLength; - if (prefix.getPrefixLength() == 0) { - optionLength = 1; - } else if (prefix.getPrefixLength() <= 64) { - optionLength = 2; - } else { - optionLength = 3; - } - - ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8); - - buffer.put((byte) ICMP6_ROUTE_INFO_OPTION_TYPE); // Type - buffer.put((byte) optionLength); // Length in 8-byte units - buffer.put((byte) prefix.getPrefixLength()); // Prefix length - buffer.put((byte) 0b00011000); // Pref = high - buffer.putInt(lifetime); // Lifetime - - byte[] prefixBytes = prefix.getRawAddress(); - buffer.put(prefixBytes, 0, (optionLength - 1) * 8); - - mPacket.write(buffer.array(), 0, buffer.capacity()); - return this; - } - - public RaPacketBuilder addDnsslOption(int lifetime, String... domains) { - ByteArrayOutputStream dnssl = new ByteArrayOutputStream(); - for (String domain : domains) { - for (String label : domain.split("\\.")) { - final byte[] bytes = label.getBytes(StandardCharsets.UTF_8); - dnssl.write((byte) bytes.length); - dnssl.write(bytes, 0, bytes.length); - } - dnssl.write((byte) 0); - } - - // Extend with 0s to make it 8-byte aligned. - while (dnssl.size() % 8 != 0) { - dnssl.write((byte) 0); - } - - final int length = ICMP6_4_BYTE_OPTION_LEN + dnssl.size(); - ByteBuffer buffer = ByteBuffer.allocate(length); - - buffer.put((byte) ICMP6_DNSSL_OPTION_TYPE); // Type - buffer.put((byte) (length / 8)); // Length - // skip past reserved bytes - buffer.position(buffer.position() + 2); - buffer.putInt(lifetime); // Lifetime - buffer.put(dnssl.toByteArray()); // Domain names - - mPacket.write(buffer.array(), 0, buffer.capacity()); - return this; - } - - public RaPacketBuilder addRdnssOption(int lifetime, String... servers) throws Exception { - int optionLength = 1 + 2 * servers.length; // In 8-byte units - ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8); - - buffer.put((byte) ICMP6_RDNSS_OPTION_TYPE); // Type - buffer.put((byte) optionLength); // Length - buffer.putShort((short) 0); // Reserved - buffer.putInt(lifetime); // Lifetime - for (String server : servers) { - buffer.put(InetAddress.getByName(server).getAddress()); - } - - mPacket.write(buffer.array(), 0, buffer.capacity()); - return this; - } - - public RaPacketBuilder addZeroLengthOption() throws Exception { - ByteBuffer buffer = ByteBuffer.allocate(ICMP6_4_BYTE_OPTION_LEN); - buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); - buffer.put((byte) 0); - - mPacket.write(buffer.array(), 0, buffer.capacity()); - return this; - } - - public byte[] build() { - ByteBuffer buffer = ByteBuffer.wrap(mPacket.toByteArray()); - // IPv6, traffic class = 0, flow label = mFlowLabel - buffer.putInt(IP_HEADER_OFFSET, 0x60000000 | (0xFFFFF & mFlowLabel)); - buffer.putShort(IPV6_PAYLOAD_LENGTH_OFFSET, (short) buffer.capacity()); - - buffer.position(ICMP6_RA_REACHABLE_TIME_OFFSET); - buffer.putInt(mReachableTime); - buffer.putInt(mRetransmissionTimer); - - return buffer.array(); - } - } - - private byte[] buildLargeRa() throws Exception { - RaPacketBuilder builder = new RaPacketBuilder(1800 /* router lft */); - - builder.addRioOption(1200, "64:ff9b::/96"); - builder.addRdnssOption(7200, "2001:db8:1::1", "2001:db8:1::2"); - builder.addRioOption(2100, "2000::/3"); - builder.addRioOption(2400, "::/0"); - builder.addPioOption(600, 300, "2001:db8:a::/64"); - builder.addRioOption(1500, "2001:db8:c:d::/64"); - builder.addPioOption(86400, 43200, "fd95:d1e:12::/64"); - - return builder.build(); - } - - // Verify that the last program pushed to the IpClient.Callback properly filters the - // given packet for the given lifetime. - private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) { - verifyRaLifetime(program, packet, lifetime, 0); - } - - // Verify that the last program pushed to the IpClient.Callback properly filters the - // given packet for the given lifetime and programInstallTime. programInstallTime is - // the time difference between when RA is last seen and the program is installed. - private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime, - int programInstallTime) { - final int FRACTION_OF_LIFETIME = 6; - final int ageLimit = lifetime / FRACTION_OF_LIFETIME - programInstallTime; - - // Verify new program should drop RA for 1/6th its lifetime and pass afterwards. - assertDrop(program, packet.array()); - assertDrop(program, packet.array(), ageLimit); - assertPass(program, packet.array(), ageLimit + 1); - assertPass(program, packet.array(), lifetime); - // Verify RA checksum is ignored - final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET); - packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345); - assertDrop(program, packet.array()); - packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345); - assertDrop(program, packet.array()); - packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum); - - // Verify other changes to RA (e.g., a change in the source address) make it not match. - final int offset = IPV6_SRC_ADDR_OFFSET + 5; - final byte originalByte = packet.get(offset); - packet.put(offset, (byte) (~originalByte)); - assertPass(program, packet.array()); - packet.put(offset, originalByte); - assertDrop(program, packet.array()); - } - - // Test that when ApfFilter is shown the given packet, it generates a program to filter it - // for the given lifetime. - private void verifyRaLifetime(TestLegacyApfFilter apfFilter, - MockIpClientCallback ipClientCallback, ByteBuffer packet, int lifetime) - throws IOException, ErrnoException { - // Verify new program generated if ApfFilter witnesses RA - apfFilter.pretendPacketReceived(packet.array()); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - verifyRaLifetime(program, packet, lifetime); - } - - private void assertInvalidRa(TestLegacyApfFilter apfFilter, - MockIpClientCallback ipClientCallback, ByteBuffer packet) - throws IOException, ErrnoException { - apfFilter.pretendPacketReceived(packet.array()); - ipClientCallback.assertNoProgramUpdate(); - } - - @Test - public void testApfFilterRa() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - final int ROUTER_LIFETIME = 1000; - final int PREFIX_VALID_LIFETIME = 200; - final int PREFIX_PREFERRED_LIFETIME = 100; - final int RDNSS_LIFETIME = 300; - final int ROUTE_LIFETIME = 400; - // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000. - final int DNSSL_LIFETIME = 2000; - - // Verify RA is passed the first time - RaPacketBuilder ra = new RaPacketBuilder(ROUTER_LIFETIME); - ByteBuffer basePacket = ByteBuffer.wrap(ra.build()); - assertPass(program, basePacket.array()); - - verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME); - - ra = new RaPacketBuilder(ROUTER_LIFETIME); - // Check that changes are ignored in every byte of the flow label. - ra.setFlowLabel(0x56789); - ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(ra.build()); - - // Ensure zero-length options cause the packet to be silently skipped. - // Do this before we test other packets. http://b/29586253 - ra = new RaPacketBuilder(ROUTER_LIFETIME); - ra.addZeroLengthOption(); - ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(ra.build()); - assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket); - - // Generate several RAs with different options and lifetimes, and verify when - // ApfFilter is shown these packets, it generates programs to filter them for the - // appropriate lifetime. - ra = new RaPacketBuilder(ROUTER_LIFETIME); - ra.addPioOption(PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, "2001:db8::/64"); - ByteBuffer prefixOptionPacket = ByteBuffer.wrap(ra.build()); - verifyRaLifetime( - apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME); - - ra = new RaPacketBuilder(ROUTER_LIFETIME); - ra.addRdnssOption(RDNSS_LIFETIME, "2001:4860:4860::8888", "2001:4860:4860::8844"); - ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(ra.build()); - verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME); - - final int lowLifetime = 60; - ra = new RaPacketBuilder(ROUTER_LIFETIME); - ra.addRdnssOption(lowLifetime, "2620:fe::9"); - ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap(ra.build()); - verifyRaLifetime(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket, - ROUTER_LIFETIME); - - ra = new RaPacketBuilder(ROUTER_LIFETIME); - ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/96"); - ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(ra.build()); - verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME); - - // Check that RIOs differing only in the first 4 bytes are different. - ra = new RaPacketBuilder(ROUTER_LIFETIME); - ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/64"); - // Packet should be passed because it is different. - program = ipClientCallback.assertProgramUpdateAndGet(); - assertPass(program, ra.build()); - - ra = new RaPacketBuilder(ROUTER_LIFETIME); - ra.addDnsslOption(DNSSL_LIFETIME, "test.example.com", "one.more.example.com"); - ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(ra.build()); - verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME); - - ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa()); - verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300); - - // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10). - program = ipClientCallback.assertProgramUpdateAndGet(); - verifyRaLifetime(program, basePacket, ROUTER_LIFETIME); - verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME); - verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME); - verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME); - verifyRaLifetime(program, lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME); - verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME); - verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME); - verifyRaLifetime(program, largeRaPacket, 300); - } - - @Test - public void testRaWithDifferentReachableTimeAndRetransTimer() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, - ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - final int RA_REACHABLE_TIME = 1800; - final int RA_RETRANSMISSION_TIMER = 1234; - - // Create an Ra packet without options - // Reachable time = 1800, retransmission timer = 1234 - RaPacketBuilder ra = new RaPacketBuilder(1800 /* router lft */); - ra.setReachableTime(RA_REACHABLE_TIME); - ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER); - byte[] raPacket = ra.build(); - // First RA passes filter - assertPass(program, raPacket); - - // Assume apf is shown the given RA, it generates program to filter it. - apfFilter.pretendPacketReceived(raPacket); - program = ipClientCallback.assertProgramUpdateAndGet(); - assertDrop(program, raPacket); - - // A packet with different reachable time should be passed. - // Reachable time = 2300, retransmission timer = 1234 - ra.setReachableTime(RA_REACHABLE_TIME + 500); - raPacket = ra.build(); - assertPass(program, raPacket); - - // A packet with different retransmission timer should be passed. - // Reachable time = 1800, retransmission timer = 2234 - ra.setReachableTime(RA_REACHABLE_TIME); - ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER + 1000); - raPacket = ra.build(); - assertPass(program, raPacket); - } - - /** - * Stage a file for testing, i.e. make it native accessible. Given a resource ID, - * copy that resource into the app's data directory and return the path to it. - */ - private String stageFile(int rawId) throws Exception { - File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file"); - new File(file.getParent()).mkdirs(); - InputStream in = null; - OutputStream out = null; - try { - in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId); - out = new FileOutputStream(file); - Streams.copy(in, out); - } finally { - if (in != null) in.close(); - if (out != null) out.close(); - } - return file.getAbsolutePath(); - } - - private static void put(ByteBuffer buffer, int position, byte[] bytes) { - final int original = buffer.position(); - buffer.position(position); - buffer.put(bytes); - buffer.position(original); - } - - @Test - public void testRaParsing() throws Exception { - final int maxRandomPacketSize = 512; - final Random r = new Random(); - MockIpClientCallback cb = new MockIpClientCallback(); - ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, - cb, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - for (int i = 0; i < 1000; i++) { - byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; - r.nextBytes(packet); - try { - apfFilter.new Ra(packet, packet.length); - } catch (LegacyApfFilter.InvalidRaException e) { - } catch (Exception e) { - throw new Exception("bad packet: " + HexDump.toHexString(packet), e); - } - } - } - - @Test - public void testRaProcessing() throws Exception { - final int maxRandomPacketSize = 512; - final Random r = new Random(); - MockIpClientCallback cb = new MockIpClientCallback(); - ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, - cb, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - for (int i = 0; i < 1000; i++) { - byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; - r.nextBytes(packet); - try { - apfFilter.processRa(packet, packet.length); - } catch (Exception e) { - throw new Exception("bad packet: " + HexDump.toHexString(packet), e); - } - } - } - - @Test - public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - // configure accept_ra_min_lft - final ApfConfiguration config = getDefaultConfig(); - config.acceptRaMinLft = 180; - TestLegacyApfFilter apfFilter; - // Template packet: - // Frame 1: 150 bytes on wire (1200 bits), 150 bytes captured (1200 bits) - // Ethernet II, Src: Netgear_23:67:2c (28:c6:8e:23:67:2c), Dst: IPv6mcast_01 (33:33:00:00:00:01) - // Internet Protocol Version 6, Src: fe80::2ac6:8eff:fe23:672c, Dst: ff02::1 - // Internet Control Message Protocol v6 - // Type: Router Advertisement (134) - // Code: 0 - // Checksum: 0x0acd [correct] - // Checksum Status: Good - // Cur hop limit: 64 - // Flags: 0xc0, Managed address configuration, Other configuration, Prf (Default Router Preference): Medium - // Router lifetime (s): 7000 - // Reachable time (ms): 0 - // Retrans timer (ms): 0 - // ICMPv6 Option (Source link-layer address : 28:c6:8e:23:67:2c) - // Type: Source link-layer address (1) - // Length: 1 (8 bytes) - // Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c) - // Source Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c) - // ICMPv6 Option (MTU : 1500) - // Type: MTU (5) - // Length: 1 (8 bytes) - // Reserved - // MTU: 1500 - // ICMPv6 Option (Prefix information : 2401:fa00:480:f000::/64) - // Type: Prefix information (3) - // Length: 4 (32 bytes) - // Prefix Length: 64 - // Flag: 0xc0, On-link flag(L), Autonomous address-configuration flag(A) - // Valid Lifetime: Infinity (4294967295) - // Preferred Lifetime: Infinity (4294967295) - // Reserved - // Prefix: 2401:fa00:480:f000:: - // ICMPv6 Option (Recursive DNS Server 2401:fa00:480:f000::1) - // Type: Recursive DNS Server (25) - // Length: 3 (24 bytes) - // Reserved - // Lifetime: 7000 - // Recursive DNS Servers: 2401:fa00:480:f000::1 - // ICMPv6 Option (Advertisement Interval : 600000) - // Type: Advertisement Interval (7) - // Length: 1 (8 bytes) - // Reserved - // Advertisement Interval: 600000 - final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0"; - final List<String> lifetimes = List.of("FFFFFFFF", "00000001", "00001B58"); - for (String lifetime : lifetimes) { - apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, - mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); - final byte[] ra = hexStringToByteArray( - String.format(packetStringFmt, lifetime + lifetime)); - // feed the RA into APF and generate the filter, the filter shouldn't crash. - apfFilter.pretendPacketReceived(ra); - ipClientCallback.assertProgramUpdateAndGet(); - } - } - - private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config, - MockIpClientCallback ipClientCallback) throws Exception { - return new TestLegacyApfFilter(mContext, config, ipClientCallback, mIpConnectivityLog, - mNetworkQuirkMetrics, mDependencies, mClock); - } - - - @Test - public void testInstallPacketFilterFailure_LegacyApfFilter() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false); - final ApfConfiguration config = getDefaultConfig(); - final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); - verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); - verify(mNetworkQuirkMetrics).statsWrite(); - reset(mNetworkQuirkMetrics); - synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); - } - verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); - verify(mNetworkQuirkMetrics).statsWrite(); - } - - @Test - public void testApfProgramOverSize_LegacyApfFilter() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - config.apfVersionSupported = 2; - config.apfRamSize = 512; - final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - final byte[] ra = buildLargeRa(); - apfFilter.pretendPacketReceived(ra); - // The generated program size will be 529, which is larger than 512 - program = ipClientCallback.assertProgramUpdateAndGet(); - verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); - verify(mNetworkQuirkMetrics).statsWrite(); - } - - @Test - public void testGenerateApfProgramException_LegacyApfFilter() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - final TestAndroidPacketFilter apfFilter; - apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, mIpConnectivityLog, - mNetworkQuirkMetrics, mDependencies, - true /* throwsExceptionWhenGeneratesProgram */); - synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); - } - verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); - verify(mNetworkQuirkMetrics).statsWrite(); - } - - @Test - public void testApfSessionInfoMetrics_LegacyApfFilter() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - config.apfVersionSupported = 4; - config.apfRamSize = 4096; - final long startTimeMs = 12345; - final long durationTimeMs = config.minMetricsSessionDurationMs; - doReturn(startTimeMs).when(mClock).elapsedRealtime(); - final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); - int maxProgramSize = 0; - int numProgramUpdated = 0; - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - maxProgramSize = Math.max(maxProgramSize, program.length); - numProgramUpdated++; - - final byte[] data = new byte[Counter.totalSize()]; - final byte[] expectedData = data.clone(); - final int totalPacketsCounterIdx = Counter.totalSize() + Counter.TOTAL_PACKETS.offset(); - final int passedIpv6IcmpCounterIdx = - Counter.totalSize() + Counter.PASSED_IPV6_ICMP.offset(); - final int droppedIpv4MulticastIdx = - Counter.totalSize() + Counter.DROPPED_IPV4_MULTICAST.offset(); - - // Receive an RA packet (passed). - final byte[] ra = buildLargeRa(); - expectedData[totalPacketsCounterIdx + 3] += 1; - expectedData[passedIpv6IcmpCounterIdx + 3] += 1; - assertDataMemoryContentsIgnoreVersion(PASS, program, ra, data, expectedData); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); - maxProgramSize = Math.max(maxProgramSize, program.length); - numProgramUpdated++; - - apfFilter.setMulticastFilter(true); - // setMulticastFilter will trigger program installation. - program = ipClientCallback.assertProgramUpdateAndGet(); - maxProgramSize = Math.max(maxProgramSize, program.length); - numProgramUpdated++; - - // Receive IPv4 multicast packet (dropped). - final byte[] multicastIpv4Addr = {(byte) 224, 0, 0, 1}; - ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP); - put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr); - expectedData[totalPacketsCounterIdx + 3] += 1; - expectedData[droppedIpv4MulticastIdx + 3] += 1; - assertDataMemoryContentsIgnoreVersion(DROP, program, mcastv4packet.array(), data, - expectedData); - - // Set data snapshot and update counters. - apfFilter.setDataSnapshot(data); - - // Write metrics data to statsd pipeline when shutdown. - doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime(); - apfFilter.shutdown(); - verify(mApfSessionInfoMetrics).setVersion(4); - verify(mApfSessionInfoMetrics).setMemorySize(4096); - - // Verify Counters - final Map<Counter, Long> expectedCounters = Map.of(Counter.TOTAL_PACKETS, 2L, - Counter.PASSED_IPV6_ICMP, 1L, Counter.DROPPED_IPV4_MULTICAST, 1L); - final ArgumentCaptor<Counter> counterCaptor = ArgumentCaptor.forClass(Counter.class); - final ArgumentCaptor<Long> valueCaptor = ArgumentCaptor.forClass(Long.class); - verify(mApfSessionInfoMetrics, times(expectedCounters.size())).addApfCounter( - counterCaptor.capture(), valueCaptor.capture()); - final List<Counter> counters = counterCaptor.getAllValues(); - final List<Long> values = valueCaptor.getAllValues(); - final ArrayMap<Counter, Long> capturedCounters = new ArrayMap<>(); - for (int i = 0; i < counters.size(); i++) { - capturedCounters.put(counters.get(i), values.get(i)); - } - assertEquals(expectedCounters, capturedCounters); - - verify(mApfSessionInfoMetrics).setApfSessionDurationSeconds( - (int) (durationTimeMs / DateUtils.SECOND_IN_MILLIS)); - verify(mApfSessionInfoMetrics).setNumOfTimesApfProgramUpdated(numProgramUpdated); - verify(mApfSessionInfoMetrics).setMaxProgramSize(maxProgramSize); - verify(mApfSessionInfoMetrics).statsWrite(); - } - - @Test - public void testIpClientRaInfoMetrics_LegacyApfFilter() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - final long startTimeMs = 12345; - final long durationTimeMs = config.minMetricsSessionDurationMs; - doReturn(startTimeMs).when(mClock).elapsedRealtime(); - final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - final int routerLifetime = 1000; - final int prefixValidLifetime = 200; - final int prefixPreferredLifetime = 100; - final int rdnssLifetime = 300; - final int routeLifetime = 400; - - // Construct 2 RAs with partial lifetimes larger than predefined constants - final RaPacketBuilder ra1 = new RaPacketBuilder(routerLifetime); - ra1.addPioOption(prefixValidLifetime + 123, prefixPreferredLifetime, "2001:db8::/64"); - ra1.addRdnssOption(rdnssLifetime, "2001:4860:4860::8888", "2001:4860:4860::8844"); - ra1.addRioOption(routeLifetime + 456, "64:ff9b::/96"); - final RaPacketBuilder ra2 = new RaPacketBuilder(routerLifetime + 123); - ra2.addPioOption(prefixValidLifetime, prefixPreferredLifetime, "2001:db9::/64"); - ra2.addRdnssOption(rdnssLifetime + 456, "2001:4860:4860::8888", "2001:4860:4860::8844"); - ra2.addRioOption(routeLifetime, "64:ff9b::/96"); - - // Construct an invalid RA packet - final RaPacketBuilder raInvalid = new RaPacketBuilder(routerLifetime); - raInvalid.addZeroLengthOption(); - - // Construct 4 different kinds of zero lifetime RAs - final RaPacketBuilder raZeroRouterLifetime = new RaPacketBuilder(0 /* routerLft */); - final RaPacketBuilder raZeroPioValidLifetime = new RaPacketBuilder(routerLifetime); - raZeroPioValidLifetime.addPioOption(0, prefixPreferredLifetime, "2001:db10::/64"); - final RaPacketBuilder raZeroRdnssLifetime = new RaPacketBuilder(routerLifetime); - raZeroRdnssLifetime.addPioOption( - prefixValidLifetime, prefixPreferredLifetime, "2001:db11::/64"); - raZeroRdnssLifetime.addRdnssOption(0, "2001:4860:4860::8888", "2001:4860:4860::8844"); - final RaPacketBuilder raZeroRioRouteLifetime = new RaPacketBuilder(routerLifetime); - raZeroRioRouteLifetime.addPioOption( - prefixValidLifetime, prefixPreferredLifetime, "2001:db12::/64"); - raZeroRioRouteLifetime.addRioOption(0, "64:ff9b::/96"); - - // Inject RA packets. Calling assertProgramUpdateAndGet()/assertNoProgramUpdate() is to make - // sure that the RA packet has been processed. - apfFilter.pretendPacketReceived(ra1.build()); - program = ipClientCallback.assertProgramUpdateAndGet(); - apfFilter.pretendPacketReceived(ra2.build()); - program = ipClientCallback.assertProgramUpdateAndGet(); - apfFilter.pretendPacketReceived(raInvalid.build()); - ipClientCallback.assertNoProgramUpdate(); - apfFilter.pretendPacketReceived(raZeroRouterLifetime.build()); - ipClientCallback.assertNoProgramUpdate(); - apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build()); - ipClientCallback.assertNoProgramUpdate(); - apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build()); - ipClientCallback.assertNoProgramUpdate(); - apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build()); - ipClientCallback.assertNoProgramUpdate(); - - // Write metrics data to statsd pipeline when shutdown. - doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime(); - apfFilter.shutdown(); - - // Verify each metric fields in IpClientRaInfoMetrics. - // LegacyApfFilter will purge expired RAs before adding new RA. Every time a new zero - // lifetime RA is received, zero lifetime RAs except the newly added one will be - // cleared, so the number of distinct RAs is 3 (ra1, ra2 and the newly added RA). - verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(3); - verify(mIpClientRaInfoMetrics).setNumberOfZeroLifetimeRas(4); - verify(mIpClientRaInfoMetrics).setNumberOfParsingErrorRas(1); - verify(mIpClientRaInfoMetrics).setLowestRouterLifetimeSeconds(routerLifetime); - verify(mIpClientRaInfoMetrics).setLowestPioValidLifetimeSeconds(prefixValidLifetime); - verify(mIpClientRaInfoMetrics).setLowestRioRouteLifetimeSeconds(routeLifetime); - verify(mIpClientRaInfoMetrics).setLowestRdnssLifetimeSeconds(rdnssLifetime); - verify(mIpClientRaInfoMetrics).statsWrite(); - } - - @Test - public void testNoMetricsWrittenForShortDuration_LegacyApfFilter() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - final long startTimeMs = 12345; - final long durationTimeMs = config.minMetricsSessionDurationMs; - - // Verify no metrics data written to statsd for duration less than durationTimeMs. - doReturn(startTimeMs).when(mClock).elapsedRealtime(); - final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); - doReturn(startTimeMs + durationTimeMs - 1).when(mClock).elapsedRealtime(); - apfFilter.shutdown(); - verify(mApfSessionInfoMetrics, never()).statsWrite(); - verify(mIpClientRaInfoMetrics, never()).statsWrite(); - - // Verify metrics data written to statsd for duration greater than or equal to - // durationTimeMs. - LegacyApfFilter.Clock clock = mock(LegacyApfFilter.Clock.class); - doReturn(startTimeMs).when(clock).elapsedRealtime(); - final TestAndroidPacketFilter apfFilter2 = new TestLegacyApfFilter(mContext, config, - ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, clock); - doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime(); - apfFilter2.shutdown(); - verify(mApfSessionInfoMetrics).statsWrite(); - verify(mIpClientRaInfoMetrics).statsWrite(); - } - - /** - * The Mock ip client callback class. - */ - private static class MockIpClientCallback extends IpClient.IpClientCallbacksWrapper { - private final ConditionVariable mGotApfProgram = new ConditionVariable(); - private byte[] mLastApfProgram; - private boolean mInstallPacketFilterReturn = true; - - MockIpClientCallback() { - super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class), - NetworkInformationShimImpl.newInstance(), false); - } - - MockIpClientCallback(boolean installPacketFilterReturn) { - super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class), - NetworkInformationShimImpl.newInstance(), false); - mInstallPacketFilterReturn = installPacketFilterReturn; - } - - @Override - public boolean installPacketFilter(byte[] filter) { - mLastApfProgram = filter; - mGotApfProgram.open(); - return mInstallPacketFilterReturn; - } - - /** - * Reset the apf program and wait for the next update. - */ - public void resetApfProgramWait() { - mGotApfProgram.close(); - } - - /** - * Assert the program is update within TIMEOUT_MS and return the program. - */ - public byte[] assertProgramUpdateAndGet() { - assertTrue(mGotApfProgram.block(TIMEOUT_MS)); - return mLastApfProgram; - } - - /** - * Assert the program is not update within TIMEOUT_MS. - */ - public void assertNoProgramUpdate() { - assertFalse(mGotApfProgram.block(TIMEOUT_MS)); - } - } - - /** - * The test legacy apf filter class. - */ - private static class TestLegacyApfFilter extends LegacyApfFilter - implements TestAndroidPacketFilter { - public static final byte[] MOCK_MAC_ADDR = {1, 2, 3, 4, 5, 6}; - private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; - - private FileDescriptor mWriteSocket; - private final MockIpClientCallback mMockIpClientCb; - private final boolean mThrowsExceptionWhenGeneratesProgram; - - TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, - NetworkQuirkMetrics networkQuirkMetrics) throws Exception { - this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, - new ApfFilter.Dependencies(context), - false /* throwsExceptionWhenGeneratesProgram */, new Clock()); - } - - TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, - NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, - boolean throwsExceptionWhenGeneratesProgram) throws Exception { - this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, - dependencies, throwsExceptionWhenGeneratesProgram, new Clock()); - } - - TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, - NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, - Clock clock) throws Exception { - this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, - dependencies, false /* throwsExceptionWhenGeneratesProgram */, clock); - } - - TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, - NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, - boolean throwsExceptionWhenGeneratesProgram, Clock clock) - throws Exception { - super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, - ipConnectivityLog, networkQuirkMetrics, dependencies, clock); - mMockIpClientCb = ipClientCallback; - mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram; - } - - /** - * Pretend an RA packet has been received and show it to LegacyApfFilter. - */ - public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException { - mMockIpClientCb.resetApfProgramWait(); - // ApfFilter's ReceiveThread will be waiting to read this. - Os.write(mWriteSocket, packet, 0, packet.length); - } - - @Override - public synchronized void maybeStartFilter() { - mHardwareAddress = MOCK_MAC_ADDR; - installNewProgramLocked(); - - // Create two sockets, "readSocket" and "mWriteSocket" and connect them together. - FileDescriptor readSocket = new FileDescriptor(); - mWriteSocket = new FileDescriptor(); - try { - Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); - } catch (ErrnoException e) { - fail(); - return; - } - // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs. - // This allows us to pretend RA packets have been received via pretendPacketReceived(). - mReceiveThread = new ReceiveThread(readSocket); - mReceiveThread.start(); - } - - @Override - public synchronized void shutdown() { - super.shutdown(); - if (mReceiveThread != null) { - mReceiveThread.halt(); - mReceiveThread = null; - } - IoUtils.closeQuietly(mWriteSocket); - } - - @Override - @GuardedBy("this") - protected ApfV4Generator emitPrologueLocked() throws - BaseApfGenerator.IllegalInstructionException { - if (mThrowsExceptionWhenGeneratesProgram) { - throw new IllegalStateException(); - } - return super.emitPrologueLocked(); - } - } -}
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java index 2fb0a7f..b01e7ea 100644 --- a/tests/unit/src/android/net/ip/IpClientTest.java +++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -851,10 +851,10 @@ ApfConfiguration.class); if (isApfSupported) { verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( - any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); + any(), any(), configCaptor.capture(), any(), any(), any()); } else { verify(mDependencies, never()).maybeCreateApfFilter( - any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); + any(), any(), configCaptor.capture(), any(), any(), any()); } return isApfSupported ? configCaptor.getValue() : null; @@ -923,7 +923,7 @@ final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass( ApfConfiguration.class); verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( - any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); + any(), any(), configCaptor.capture(), any(), any(), any()); final ApfConfiguration actual = configCaptor.getValue(); assertNotNull(actual); assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, actual.apfVersionSupported); @@ -958,7 +958,7 @@ ipc.updateApfCapabilities(newApfCapabilities); HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(), - any(), anyBoolean()); + any()); verifyShutdown(ipc); } @@ -974,15 +974,15 @@ ipc.updateApfCapabilities(null /* apfCapabilities */); HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(), - any(), anyBoolean()); + any()); verifyShutdown(ipc); } @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void testVendorNdOffloadDisabledWhenApfV6Supported() throws Exception { - when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(), - anyBoolean())).thenReturn(mApfFilter); + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), + any())).thenReturn(mApfFilter); when(mApfFilter.supportNdOffload()).thenReturn(true); final IpClient ipc = makeIpClient(TEST_IFNAME); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() @@ -1007,8 +1007,8 @@ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void testVendorNdOffloadEnabledWhenApfV6NotSupported() throws Exception { - when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(), - anyBoolean())).thenReturn(mApfFilter); + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), + any())).thenReturn(mApfFilter); when(mApfFilter.supportNdOffload()).thenReturn(false); final IpClient ipc = makeIpClient(TEST_IFNAME); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() @@ -1031,8 +1031,8 @@ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void testVendorNdOffloadDisabledWhenApfCapabilitiesUpdated() throws Exception { - when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(), - anyBoolean())).thenReturn(mApfFilter); + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), + any())).thenReturn(mApfFilter); when(mApfFilter.supportNdOffload()).thenReturn(true); final IpClient ipc = makeIpClient(TEST_IFNAME); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() @@ -1054,8 +1054,8 @@ @Test public void testLinkPropertiesUpdate_callSetLinkPropertiesOnApfFilter() throws Exception { - when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(), - anyBoolean())).thenReturn(mApfFilter); + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), + any())).thenReturn(mApfFilter); final IpClient ipc = makeIpClient(TEST_IFNAME); verifyApfFilterCreatedOnStart(ipc, true /* isApfSupported */); onInterfaceAddressUpdated(
diff --git a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java index 69464cf..b8a0407 100644 --- a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java +++ b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
@@ -102,8 +102,6 @@ verifyCounterName(Counter.PASSED_IPV6_ICMP, CounterName.CN_PASSED_IPV6_ICMP); verifyCounterName(Counter.PASSED_IPV6_UNICAST_NON_ICMP, CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP); - verifyCounterName(Counter.PASSED_ARP_NON_IPV4, CounterName.CN_UNKNOWN); - verifyCounterName(Counter.PASSED_ARP_UNKNOWN, CounterName.CN_UNKNOWN); verifyCounterName(Counter.PASSED_ARP_UNICAST_REPLY, CounterName.CN_PASSED_ARP_UNICAST_REPLY); verifyCounterName(Counter.PASSED_NON_IP_UNICAST, CounterName.CN_PASSED_NON_IP_UNICAST); @@ -140,7 +138,8 @@ verifyCounterName(Counter.DROPPED_IPV4_NATT_KEEPALIVE, CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE); verifyCounterName(Counter.DROPPED_MDNS, CounterName.CN_DROPPED_MDNS); - verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST, CounterName.CN_UNKNOWN); + verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST, + CounterName.CN_DROPPED_IPV4_TCP_PORT7_UNICAST); verifyCounterName(Counter.DROPPED_ARP_NON_IPV4, CounterName.CN_DROPPED_ARP_NON_IPV4); verifyCounterName(Counter.DROPPED_ARP_UNKNOWN, CounterName.CN_DROPPED_ARP_UNKNOWN); verifyCounterName(Counter.PASSED_ARP_BROADCAST_REPLY,
diff --git a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt index 7f8cacb..f23f7f6 100644 --- a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt +++ b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
@@ -18,9 +18,11 @@ import android.net.MacAddress import android.net.apf.ProcfsParsingUtils import androidx.test.filters.SmallTest -import com.android.internal.util.HexDump +import com.android.net.module.util.HexDump +import java.net.Inet4Address import java.net.Inet6Address import java.net.InetAddress +import java.nio.ByteOrder import kotlin.test.assertEquals import org.junit.Test @@ -135,4 +137,149 @@ ProcfsParsingUtils.parseIPv6MulticastAddresses(inputString, "wlan0") ) } + + @Test + fun testParseIpv4MulticastAddressLittleEndian() { + val order = ByteOrder.LITTLE_ENDIAN + + // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show + val inputString = listOf( + "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter", + "1\tlo : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "2\tdummy0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "47\twlan0 : 1 V3", + "\t\t\t\t020000EF 1 0:00000000\t\t0", + "\t\t\t\t010000EF 1 0:00000000\t\t0", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "51\tv4-wlan0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0" + ) + + val expectedResult = listOf( + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000002") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000001") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("E0000001") + ) as Inet4Address, + ) + + assertEquals( + expectedResult, + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "wlan0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "eth0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + emptyList<String>(), "eth0", order) + ) + } + + @Test + fun testParseIpv4MulticastAddressBigEndian() { + val order = ByteOrder.BIG_ENDIAN + + // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show + val inputString = listOf( + "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter", + "1\tlo : 1 V3", + "\t\t\t\tE0000001 1 0:00000000\t\t0", + "2\tdummy0 : 1 V3", + "\t\t\t\tE0000001 1 0:00000000\t\t0", + "47\twlan0 : 1 V3", + "\t\t\t\tEF000002 1 0:00000000\t\t0", + "\t\t\t\tEF000001 1 0:00000000\t\t0", + "\t\t\t\tE0000001 1 0:00000000\t\t0", + "51\tv4-wlan0 : 1 V3", + "\t\t\t\tE0000001 1 0:00000000\t\t0" + ) + + val expectedResult = listOf( + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000002") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000001") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("E0000001") + ) as Inet4Address, + ) + + assertEquals( + expectedResult, + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "wlan0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "eth0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + emptyList<String>(), "eth0", order) + ) + } + + @Test + fun testParseIpv4MulticastAddressError() { + val order = ByteOrder.LITTLE_ENDIAN + + // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show + // wlan0 addresses contain invalid char 'X' + val inputString = listOf( + "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter", + "1\tlo : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "2\tdummy0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "47\twlan0 : 1 V3", + "\t\t\t\t02XXXXEF 1 0:00000000\t\t0", + "\t\t\t\t01XXXXEF 1 0:00000000\t\t0", + "\t\t\t\t01XXXXE0 1 0:00000000\t\t0", + "51\tv4-wlan0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0" + ) + + val expectedResult = listOf( + InetAddress.getByAddress( + HexDump.hexStringToByteArray("E0000001") + ) as Inet4Address + ) + + assertEquals( + expectedResult, + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "wlan0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "eth0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + emptyList<String>(), "eth0", order) + ) + } }