Snap for 12359238 from 9b8fc71c1070e391c0c3f54ef2ca89bed2fe1b4f to android15-tests-release Change-Id: I3b1a97f3a8e2001ae83628c17be58d5ac19bd87b
diff --git a/Android.bp b/Android.bp index 3712ba6..1228079 100644 --- a/Android.bp +++ b/Android.bp
@@ -14,32 +14,6 @@ // limitations under the License. // -// The network stack can be compiled using system_current (non-finalized) SDK, or finalized system_X -// SDK. There is also a variant that uses system_current SDK and runs in the system process -// (InProcessNetworkStack). The following structure is used to create the build rules: -// -// NetworkStackAndroidLibraryDefaults <-- common defaults for android libs -// / \ -// +NetworkStackApiStableShims --> / \ <-- +NetworkStackApiCurrentShims -// +NetworkStackReleaseApiLevel / \ +NetworkStackDevApiLevel -// +jarjar apishim.api[latest].* / \ -// to apishim.* / \ -// / \ -// / \ -// / \ android libs w/ all code -// / <- +module src/ -> \ (also used in unit tests) -// / \ | -// NetworkStackApiStableLib NetworkStackApiCurrentLib <--* -// | | -// | <-- +NetworkStackAppDefaults --> | -// | (APK build params) | -// | | -// | <-- +NetworkStackReleaseApiLevel | <-- +NetworkStackDevApiLevel -// | | -// | | -// NetworkStackApiStable NetworkStack, InProcessNetworkStack, <-- APKs -// TestNetworkStack - // Common defaults to define SDK level package { default_team: "trendy_team_fwk_core_networking", @@ -56,23 +30,6 @@ enabled: true, } -// This is a placeholder comment to avoid merge conflicts -// as the above target may have different "enabled" values -// depending on the branch - -java_defaults { - name: "NetworkStackDevApiLevel", - min_sdk_version: "30", - sdk_version: "module_current", - libs: [ - "framework-configinfrastructure", - "framework-connectivity", - "framework-connectivity-t", - "framework-statsd", - "framework-wifi", - ], -} - // Common defaults for NetworkStack integration tests, root tests and coverage tests // to keep tests always running against the same target sdk version with NetworkStack. java_defaults { @@ -90,6 +47,7 @@ "framework-connectivity.stubs.module_lib", "framework-connectivity-t", "framework-statsd", + "framework-tethering", "framework-wifi", ], } @@ -262,7 +220,7 @@ name: "NetworkStackApiCurrentShims", defaults: [ "NetworkStackShimsDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: [ @@ -343,7 +301,7 @@ android_library { name: "NetworkStackApiCurrentLib", defaults: [ - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults", "ConnectivityNextEnableDefaults", ], @@ -484,7 +442,7 @@ name: "InProcessNetworkStack", defaults: [ "NetworkStackAppDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackApiCurrentLib"], @@ -507,7 +465,7 @@ name: "NetworkStackNextManifestBase", defaults: [ "NetworkStackAppDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackApiCurrentLib"], @@ -519,7 +477,7 @@ name: "NetworkStackNext", defaults: [ "NetworkStackAppDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackNextManifestBase"],
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 22591f0..32d423b 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml
@@ -22,5 +22,5 @@ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Εμφανίζονται ειδοποιήσεις για να υποδείξουν ότι το δίκτυο διαθέτει σελίδα πληροφοριών για τον τόπο."</string> <string name="connected" msgid="4563643884927480998">"Συνδέθηκε"</string> <string name="tap_for_info" msgid="6849746325626883711">"Συνδέθηκε/Πατήστε για προβολή του ιστοτόπου"</string> - <string name="application_label" msgid="1322847171305285454">"Διαχείριση δικτύου"</string> + <string name="application_label" msgid="1322847171305285454">"Διαχειριστής δικτύου"</string> </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 451d8c3..b595a9e 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml
@@ -21,6 +21,6 @@ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"اطلاعات محل شبکه"</string> <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"اعلانهایی که نمایش داده میشوند تا نشان دهند شبکه صفحه اطلاعات محل دارد"</string> <string name="connected" msgid="4563643884927480998">"متصل"</string> - <string name="tap_for_info" msgid="6849746325626883711">"متصل شد / برای مشاهده وبسایت تکضرب بزنید"</string> + <string name="tap_for_info" msgid="6849746325626883711">"متصل شد / برای مشاهده وبسایت ضربه بزنید"</string> <string name="application_label" msgid="1322847171305285454">"مدیر شبکه"</string> </resources>
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java index 8c7ff05..6dd4fad 100644 --- a/src/android/net/apf/AndroidPacketFilter.java +++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -15,7 +15,6 @@ */ package android.net.apf; -import android.annotation.NonNull; import android.annotation.Nullable; import android.net.LinkProperties; import android.net.NattKeepalivePacketDataParcelable; @@ -104,7 +103,14 @@ * Determines whether the APF interpreter advertises support for the data buffer access * opcodes LDDW (LoaD Data Word) and STDW (STore Data Word). */ - default boolean hasDataAccess(@NonNull ApfCapabilities capabilities) { - return capabilities.apfVersionSupported > 2; + default boolean hasDataAccess(int apfVersionSupported) { + return apfVersionSupported > 2; + } + + /** + * Whether the ApfFilter supports generating ND offload code. + */ + default boolean supportNdOffload() { + return false; } }
diff --git a/src/android/net/apf/ApfConstants.java b/src/android/net/apf/ApfConstants.java index fe2cfd8..09be67b 100644 --- a/src/android/net/apf/ApfConstants.java +++ b/src/android/net/apf/ApfConstants.java
@@ -65,7 +65,40 @@ public static final int ICMP6_CHECKSUM_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 2; public static final int ICMP6_NS_TARGET_IP_OFFSET = ICMP6_TYPE_OFFSET + 8; public static final int ICMP6_NS_OPTION_TYPE_OFFSET = ICMP6_NS_TARGET_IP_OFFSET + 16; + // From RFC4861: + public static final int ICMP6_RA_HEADER_LEN = 16; + public static final int ICMP6_RA_CHECKSUM_OFFSET = + ETH_HEADER_LEN + IPV6_HEADER_LEN + 2; + public static final int ICMP6_RA_CHECKSUM_LEN = 2; + public static final int ICMP6_RA_OPTION_OFFSET = + ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN; + public static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET = + ETH_HEADER_LEN + IPV6_HEADER_LEN + 6; + public static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2; + // Prefix information option. + public static final int ICMP6_PREFIX_OPTION_TYPE = 3; + public static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; + public static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4; + public static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4; + // From RFC4861: source link-layer address + public static final int ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE = 1; + // From RFC4861: mtu size option + public static final int ICMP6_MTU_OPTION_TYPE = 5; + // From RFC6106: Recursive DNS Server option + public static final int ICMP6_RDNSS_OPTION_TYPE = 25; + // From RFC6106: DNS Search List option + public static final int ICMP6_DNSSL_OPTION_TYPE = 31; + // From RFC8910: Captive-Portal option + public static final int ICMP6_CAPTIVE_PORTAL_OPTION_TYPE = 37; + // From RFC8781: PREF64 option + public static final int ICMP6_PREF64_OPTION_TYPE = 38; + + // From RFC4191: Route Information option + public static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; + // Above three options all have the same format: + public static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4; + public static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; public static final int IPPROTO_HOPOPTS = 0; // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java index e86aab1..6c5b566 100644 --- a/src/android/net/apf/ApfCounterTracker.java +++ b/src/android/net/apf/ApfCounterTracker.java
@@ -50,6 +50,7 @@ APF_VERSION, APF_PROGRAM_ID, // TODO: removing PASSED_ARP after remove LegacyApfFilter.java + // The counter sequence should keep the same as ApfSessionInfoMetrics.java PASSED_ARP, // see also MIN_PASS_COUNTER below. PASSED_ARP_BROADCAST_REPLY, // TODO: removing PASSED_ARP_NON_IPV4 after remove LegacyApfFilter.java @@ -63,8 +64,10 @@ PASSED_IPV4_UNICAST, PASSED_IPV6_ICMP, PASSED_IPV6_NON_ICMP, - PASSED_IPV6_NS_MULTIPLE_OPTIONS, + PASSED_IPV6_NS_DAD, PASSED_IPV6_NS_NO_ADDRESS, + PASSED_IPV6_NS_NO_SLLA_OPTION, + PASSED_IPV6_NS_TENTATIVE, PASSED_IPV6_UNICAST_NON_ICMP, PASSED_NON_IP_UNICAST, PASSED_MDNS, @@ -83,6 +86,7 @@ DROPPED_IPV6_NON_ICMP_MULTICAST, DROPPED_IPV6_NS_INVALID, DROPPED_IPV6_NS_OTHER_HOST, + DROPPED_IPV6_NS_REPLIED_NON_DAD, DROPPED_802_3_FRAME, DROPPED_ETHERTYPE_NOT_ALLOWED, DROPPED_IPV4_KEEPALIVE_ACK,
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 3aee15e..c8e43d0 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -36,10 +36,28 @@ import static android.net.apf.ApfConstants.ETH_TYPE_MAX; import static android.net.apf.ApfConstants.ETH_TYPE_MIN; import static android.net.apf.ApfConstants.FIXED_ARP_REPLY_HEADER; +import static android.net.apf.ApfConstants.ICMP6_4_BYTE_LIFETIME_LEN; +import static android.net.apf.ApfConstants.ICMP6_4_BYTE_LIFETIME_OFFSET; +import static android.net.apf.ApfConstants.ICMP6_CAPTIVE_PORTAL_OPTION_TYPE; import static android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET; import static android.net.apf.ApfConstants.ICMP6_CODE_OFFSET; +import static android.net.apf.ApfConstants.ICMP6_DNSSL_OPTION_TYPE; +import static android.net.apf.ApfConstants.ICMP6_MTU_OPTION_TYPE; import static android.net.apf.ApfConstants.ICMP6_NS_OPTION_TYPE_OFFSET; import static android.net.apf.ApfConstants.ICMP6_NS_TARGET_IP_OFFSET; +import static android.net.apf.ApfConstants.ICMP6_PREF64_OPTION_TYPE; +import static android.net.apf.ApfConstants.ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN; +import static android.net.apf.ApfConstants.ICMP6_PREFIX_OPTION_TYPE; +import static android.net.apf.ApfConstants.ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN; +import static android.net.apf.ApfConstants.ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET; +import static android.net.apf.ApfConstants.ICMP6_RA_CHECKSUM_LEN; +import static android.net.apf.ApfConstants.ICMP6_RA_CHECKSUM_OFFSET; +import static android.net.apf.ApfConstants.ICMP6_RA_OPTION_OFFSET; +import static android.net.apf.ApfConstants.ICMP6_RA_ROUTER_LIFETIME_LEN; +import static android.net.apf.ApfConstants.ICMP6_RA_ROUTER_LIFETIME_OFFSET; +import static android.net.apf.ApfConstants.ICMP6_RDNSS_OPTION_TYPE; +import static android.net.apf.ApfConstants.ICMP6_ROUTE_INFO_OPTION_TYPE; +import static android.net.apf.ApfConstants.ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE; import static android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET; import static android.net.apf.ApfConstants.IPPROTO_HOPOPTS; import static android.net.apf.ApfConstants.IPV4_ANY_HOST_ADDRESS; @@ -60,13 +78,18 @@ import static android.net.apf.ApfConstants.IPV6_PAYLOAD_LEN_OFFSET; import static android.net.apf.ApfConstants.IPV6_SOLICITED_NODES_PREFIX; import static android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET; +import static android.net.apf.ApfConstants.IPV6_UNSPECIFIED_ADDRESS; import static android.net.apf.ApfConstants.MDNS_PORT; import static android.net.apf.ApfConstants.TCP_HEADER_SIZE_OFFSET; import static android.net.apf.ApfConstants.TCP_UDP_DESTINATION_PORT_OFFSET; import static android.net.apf.ApfConstants.TCP_UDP_SOURCE_PORT_OFFSET; import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID; import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST; -import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_MULTIPLE_OPTIONS; +import static android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS; +import static android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_SECONDS; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE; import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS; import static android.net.apf.BaseApfGenerator.MemorySlot; import static android.net.apf.BaseApfGenerator.Register.R0; @@ -75,7 +98,6 @@ import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; import static android.system.OsConstants.AF_PACKET; -import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; @@ -92,6 +114,7 @@ import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA_LEN; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; @@ -180,17 +203,18 @@ // Helper class for specifying functional filter parameters. public static class ApfConfiguration { - public ApfCapabilities apfCapabilities; + public int apfVersionSupported; + public int apfRamSize; public int installableProgramSizeClamp = Integer.MAX_VALUE; public boolean multicastFilter; public boolean ieee802_3Filter; public int[] ethTypeBlackList; public int minRdnssLifetimeSec; public int acceptRaMinLft; - public boolean shouldHandleLightDoze; public long minMetricsSessionDurationMs; public boolean hasClatInterface; public boolean shouldHandleArpOffload; + public boolean shouldHandleNdOffload; } /** A wrapper class of {@link SystemClock} to be mocked in unit tests. */ @@ -241,15 +265,18 @@ private static final boolean DBG = true; private static final boolean VDBG = false; - private final ApfCapabilities mApfCapabilities; + private final int mApfRamSize; + private final int mMaximumApfProgramSize; private final int mInstallableProgramSizeClamp; private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; private final TokenBucket mTokenBucket; @VisibleForTesting + public final int mApfVersionSupported; + @VisibleForTesting @NonNull - public byte[] mHardwareAddress; + public final byte[] mHardwareAddress; @VisibleForTesting public ReceiveThread mReceiveThread; @GuardedBy("this") @@ -261,7 +288,6 @@ private final boolean mDrop802_3Frames; private final int[] mEthTypeBlackList; - private final Clock mClock; private final ApfCounterTracker mApfCounterTracker = new ApfCounterTracker(); @GuardedBy("this") private final long mSessionStartMs; @@ -287,8 +313,8 @@ // Tracks the value of /proc/sys/ipv6/conf/$iface/accept_ra_min_lft which affects router, RIO, // and PIO valid lifetimes. private final int mAcceptRaMinLft; - private final boolean mShouldHandleLightDoze; private final boolean mShouldHandleArpOffload; + private final boolean mShouldHandleNdOffload; private final NetworkQuirkMetrics mNetworkQuirkMetrics; private final IpClientRaInfoMetrics mIpClientRaInfoMetrics; @@ -305,9 +331,6 @@ if (!SdkLevel.isAtLeastT()) { return false; } - if (!mShouldHandleLightDoze) { - return false; - } return ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(intent.getAction()); } @@ -318,9 +341,6 @@ if (!SdkLevel.isAtLeastT()) { return false; } - if (!mShouldHandleLightDoze) { - return false; - } return powerManager.isDeviceLightIdleMode(); } @@ -368,37 +388,39 @@ public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics) { this(context, config, ifParams, ipClientCallback, networkQuirkMetrics, - new Dependencies(context), new Clock()); + new Dependencies(context)); } @VisibleForTesting public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, Dependencies dependencies) { - this(context, config, ifParams, ipClientCallback, networkQuirkMetrics, dependencies, - new Clock()); - } - - @VisibleForTesting - public ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, - Dependencies dependencies, Clock clock) { - mApfCapabilities = config.apfCapabilities; + mApfVersionSupported = config.apfVersionSupported; + mApfRamSize = config.apfRamSize; mInstallableProgramSizeClamp = config.installableProgramSizeClamp; + int maximumApfProgramSize = mApfRamSize; + if (hasDataAccess(mApfVersionSupported)) { + // Reserve space for the counters. + maximumApfProgramSize -= Counter.totalSize(); + } + // Prevent generating (and thus installing) larger programs + if (maximumApfProgramSize > mInstallableProgramSizeClamp) { + maximumApfProgramSize = mInstallableProgramSizeClamp; + } + mMaximumApfProgramSize = maximumApfProgramSize; mIpClientCallback = ipClientCallback; mInterfaceParams = ifParams; mMulticastFilter = config.multicastFilter; mDrop802_3Frames = config.ieee802_3Filter; mMinRdnssLifetimeSec = config.minRdnssLifetimeSec; mAcceptRaMinLft = config.acceptRaMinLft; - mShouldHandleLightDoze = config.shouldHandleLightDoze; mShouldHandleArpOffload = config.shouldHandleArpOffload; + mShouldHandleNdOffload = config.shouldHandleNdOffload; mDependencies = dependencies; mNetworkQuirkMetrics = networkQuirkMetrics; mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics(); mApfSessionInfoMetrics = dependencies.getApfSessionInfoMetrics(); - mClock = clock; - mSessionStartMs = mClock.elapsedRealtime(); + mSessionStartMs = dependencies.elapsedRealtime(); mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs; mHasClat = config.hasClatInterface; @@ -414,15 +436,18 @@ // 3 seconds. mTokenBucket = new TokenBucket(3_000 /* deltaMs */, 20 /* capacity */, 20 /* tokens */); + mHardwareAddress = mInterfaceParams.macAddr.toByteArray(); // TODO: ApfFilter should not generate programs until IpClient sends provisioning success. - maybeStartFilter(); + startFilter(); // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter. - mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver, mShouldHandleLightDoze); + mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver); mDependencies.onApfFilterCreated(this); - // mReceiveThread is created in maybeStartFilter() and halted in shutdown(). - mDependencies.onThreadCreated(mReceiveThread); + // mReceiveThread is created in startFilter() and halted in shutdown(). + if (mReceiveThread != null) { + mDependencies.onThreadCreated(mReceiveThread); + } } /** @@ -435,11 +460,35 @@ mContext = context; } + /** + * Create a socket to read RAs. + */ + @Nullable + public FileDescriptor createRaReaderSocket(int ifIndex) { + FileDescriptor socket; + try { + socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0); + NetworkStackUtils.attachRaFilter(socket); + SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, ifIndex); + Os.bind(socket, addr); + } catch (SocketException | ErrnoException e) { + Log.wtf(TAG, "Error starting filter", e); + return null; + } + return socket; + } + + /** + * Get elapsedRealtime. + */ + public long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + /** Add receiver for detecting doze mode change */ - public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver, - boolean shouldHandleLightDoze) { + public void addDeviceIdleReceiver(@NonNull final BroadcastReceiver receiver) { final IntentFilter intentFilter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED); - if (SdkLevel.isAtLeastT() && shouldHandleLightDoze) { + if (SdkLevel.isAtLeastT()) { intentFilter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED); } mContext.registerReceiver(receiver, intentFilter); @@ -574,40 +623,32 @@ * 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(mApfCapabilities)) { - byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize]; - if (!mIpClientCallback.installPacketFilter(zeroes)) { - sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); - } + public void startFilter() { + synchronized (this) { + // Clear the APF memory to reset all counters upon connecting to the first AP + // in an SSID. This is limited to APFv3 devices because this large write triggers + // a crash on some older devices (b/78905546). + if (hasDataAccess(mApfVersionSupported)) { + byte[] zeroes = new byte[mApfRamSize]; + if (!mIpClientCallback.installPacketFilter(zeroes)) { + sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); } - - // Install basic filters - installNewProgramLocked(); } - socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0); - NetworkStackUtils.attachRaFilter(socket); - SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index); - Os.bind(socket, addr); - } catch(SocketException|ErrnoException e) { - Log.e(TAG, "Error starting filter", e); - return; + + // Install basic filters + installNewProgramLocked(); } - mReceiveThread = new ReceiveThread(socket); - mReceiveThread.start(); + FileDescriptor socket = mDependencies.createRaReaderSocket(mInterfaceParams.index); + if (socket != null) { + mReceiveThread = new ReceiveThread(socket); + mReceiveThread.start(); + } } // Returns seconds since device boot. @VisibleForTesting protected int secondsSinceBoot() { - return (int) (mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS); + return (int) (mDependencies.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS); } public static class InvalidRaException extends Exception { @@ -669,41 +710,6 @@ // 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_VALID_LIFETIME_OFFSET = 4; - private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4; - private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4; - - // From RFC4861: source link-layer address - private static final int ICMP6_SOURCE_LL_ADDRESS_OPTION_TYPE = 1; - // From RFC4861: mtu size option - private static final int ICMP6_MTU_OPTION_TYPE = 5; - // 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 RFC8910: Captive-Portal option - private static final int ICMP6_CAPTIVE_PORTAL_OPTION_TYPE = 37; - // From RFC8781: PREF64 option - private static final int ICMP6_PREF64_OPTION_TYPE = 38; - - // 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; @@ -1934,22 +1940,63 @@ v6Gen.addLoad8(R0, ICMP6_CODE_OFFSET) .addCountAndDropIfR0NotEquals(0, DROPPED_IPV6_NS_INVALID); - // target address (ICMPv6 NS/NA payload) is not interface addresses -> drop - v6Gen.addLoadImmediate(R0, ICMP6_NS_TARGET_IP_OFFSET) - .addCountAndDropIfBytesAtR0EqualsNoneOf(allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST); + // target address (ICMPv6 NS payload) + // 1) is one of tentative addresses -> pass + // 2) is none of {non-tentative, anycast} addresses -> drop + final List<byte[]> tentativeIPv6Addrs = getIpv6Addresses( + false, /* includeNonTentative */ + true, /* includeTentative */ + false /* includeAnycast */ + ); + v6Gen.addLoadImmediate(R0, ICMP6_NS_TARGET_IP_OFFSET); + if (!tentativeIPv6Addrs.isEmpty()) { + v6Gen.addCountAndPassIfBytesAtR0EqualsAnyOf( + tentativeIPv6Addrs, PASSED_IPV6_NS_TENTATIVE); + } - // Only offload the following cases: - // 1) NS packet with no options. - // 2) NS packet with only one option: nonce. - // 3) NS packet with only one option: SLLA. - // For packets containing more than one option, - // pass the packet to the CPU for processing. - // payload length > 32 - // (8 bytes ICMP6 header + 16 bytes target address + 8 bytes option) -> pass + final List<byte[]> nonTentativeIpv6Addrs = getIpv6Addresses( + true, /* includeNonTentative */ + false, /* includeTentative */ + true /* includeAnycast */ + ); + if (nonTentativeIpv6Addrs.isEmpty()) { + v6Gen.addCountAndDrop(DROPPED_IPV6_NS_OTHER_HOST); + return; + } + v6Gen.addCountAndDropIfBytesAtR0EqualsNoneOf( + nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST); + + // if source ip is unspecified (::), it's DAD request -> pass + v6Gen.addLoadImmediate(R0, IPV6_SRC_ADDR_OFFSET) + .addCountAndPassIfBytesAtR0Equal(IPV6_UNSPECIFIED_ADDRESS, PASSED_IPV6_NS_DAD); + + // Only offload NUD/Address resolution packets that have SLLA as the their first option. + // For option-less NUD packets or NUD/Address resolution packets where + // the first option is not SLLA, pass them to the kernel for handling. + // if payload len < 32 -> pass v6Gen.addLoad16(R0, IPV6_PAYLOAD_LEN_OFFSET) - .addCountAndPassIfR0GreaterThan(32, PASSED_IPV6_NS_MULTIPLE_OPTIONS); + .addCountAndPassIfR0LessThan(32, PASSED_IPV6_NS_NO_SLLA_OPTION); - v6Gen.addCountAndPass(Counter.PASSED_IPV6_ICMP); + // if the first option is not SLLA -> pass + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Length |Link-Layer Addr | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + v6Gen.addLoad8(R0, ICMP6_NS_OPTION_TYPE_OFFSET) + .addCountAndPassIfR0NotEquals(ICMPV6_ND_OPTION_SLLA, + PASSED_IPV6_NS_NO_SLLA_OPTION); + + // Src IPv6 address check: + // if multicast address (FF::/8) or loopback address (00::/8) -> drop + v6Gen.addLoad8(R0, IPV6_SRC_ADDR_OFFSET) + .addCountAndDropIfR0IsOneOf(Set.of(0L, 0xffL), DROPPED_IPV6_NS_INVALID); + + // if multicast MAC in SLLA option -> drop + v6Gen.addLoad8(R0, ICMP6_NS_OPTION_TYPE_OFFSET + 2) + .addCountAndDropIfR0AnyBitsSet(1, DROPPED_IPV6_NS_INVALID); + generateNonDadNaTransmitLocked(v6Gen); + v6Gen.addCountAndDrop(Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD); } /** @@ -1986,10 +2033,19 @@ // drop // if ICMPv6 code is not 0: // drop - // if target IP is none of interface unicast IPv6 addresses (incl. anycast): - // drop - // if payload len > 32 (8 bytes ICMP6 header + 16 bytes target address + 8 bytes option): + // if target IP is one of tentative IPv6 addresses: // pass + // if target IP is none of non-tentative IPv6 addresses (incl. anycast): + // drop + // if IPv6 src is unspecified (::): + // pass + // if payload len < 32 (8 bytes ICMP6 header + 16 bytes target address + 8 bytes option): + // pass + // if IPv6 src is multicast address (FF::/8) or loopback address (00::/8): + // drop + // if multicast MAC in SLLA option: + // drop + // transmit NA and drop // if it's ICMPv6 RS to any: // drop // if it's ICMPv6 NA to anything in ff02::/120 @@ -2041,7 +2097,7 @@ // Not ICMPv6 NS -> skip. gen.addLoad8(R0, ICMP6_TYPE_OFFSET); // warning: also used further below. final ApfV6Generator v6Gen = tryToConvertToApfV6Generator(gen); - if (v6Gen != null) { + if (v6Gen != null && mShouldHandleNdOffload) { final String skipNsPacketFilter = v6Gen.getUniqueLabel(); v6Gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_SOLICITATION, skipNsPacketFilter); generateNsFilterLocked(v6Gen); @@ -2202,17 +2258,18 @@ */ @GuardedBy("this") @VisibleForTesting - protected ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException { + public ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. ApfV4GeneratorBase<?> gen; - if (SdkLevel.isAtLeastV() - && ApfV6Generator.supportsVersion(mApfCapabilities.apfVersionSupported)) { - gen = new ApfV6Generator(mApfCapabilities.maximumApfProgramSize); + if (shouldUseApfV6Generator()) { + gen = new ApfV6Generator(mApfVersionSupported, mApfRamSize, + mInstallableProgramSizeClamp); } else { - gen = new ApfV4Generator(mApfCapabilities.apfVersionSupported); + gen = new ApfV4Generator(mApfVersionSupported, mApfRamSize, + mInstallableProgramSizeClamp); } - if (hasDataAccess(mApfCapabilities)) { + if (hasDataAccess(mApfVersionSupported)) { if (gen instanceof ApfV4Generator) { // Increment TOTAL_PACKETS. // Only needed in APFv4. @@ -2333,16 +2390,6 @@ ArrayList<Ra> rasToFilter = new ArrayList<>(); final byte[] program; int programMinLft = Integer.MAX_VALUE; - int maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize; - if (hasDataAccess(mApfCapabilities)) { - // Reserve space for the counters. - maximumApfProgramSize -= Counter.totalSize(); - } - - // Prevent generating (and thus installing) larger programs - if (maximumApfProgramSize > mInstallableProgramSizeClamp) { - maximumApfProgramSize = mInstallableProgramSizeClamp; - } // Ensure the entire APF program uses the same time base. int timeSeconds = secondsSinceBoot(); @@ -2355,8 +2402,8 @@ emitEpilogue(gen); // Can't fit the program even without any RA filters? - if (gen.programLengthOverEstimate() > maximumApfProgramSize) { - Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize); + if (gen.programLengthOverEstimate() > mMaximumApfProgramSize) { + Log.e(TAG, "Program exceeds maximum size " + mMaximumApfProgramSize); sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); return; } @@ -2366,7 +2413,7 @@ if (ra.getRemainingFilterLft(timeSeconds) <= 0) continue; ra.generateFilterLocked(gen, timeSeconds); // Stop if we get too big. - if (gen.programLengthOverEstimate() > maximumApfProgramSize) { + if (gen.programLengthOverEstimate() > mMaximumApfProgramSize) { if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs"); sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); break; @@ -2509,18 +2556,11 @@ InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics) { if (context == null || config == null || ifParams == null) return null; - ApfCapabilities apfCapabilities = config.apfCapabilities; - if (apfCapabilities == null) return null; - if (apfCapabilities.apfVersionSupported < 2) return null; - if (apfCapabilities.maximumApfProgramSize < 512) { - Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize); + if (!ApfV4Generator.supportsVersion(config.apfVersionSupported)) { return null; } - // For now only support generating programs for Ethernet frames. If this restriction is - // lifted the program generator will need its offsets adjusted. - if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null; - if (!ApfV4Generator.supportsVersion(apfCapabilities.apfVersionSupported)) { - Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); + if (config.apfRamSize < 512) { + Log.e(TAG, "Unacceptably small APF limit: " + config.apfRamSize); return null; } @@ -2529,7 +2569,7 @@ private synchronized void collectAndSendMetrics() { if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return; - final long sessionDurationMs = mClock.elapsedRealtime() - mSessionStartMs; + final long sessionDurationMs = mDependencies.elapsedRealtime() - mSessionStartMs; if (sessionDurationMs < mMinMetricsSessionDurationMs) return; // Collect and send IpClientRaInfoMetrics. @@ -2543,8 +2583,8 @@ mIpClientRaInfoMetrics.statsWrite(); // Collect and send ApfSessionInfoMetrics. - mApfSessionInfoMetrics.setVersion(mApfCapabilities.apfVersionSupported); - mApfSessionInfoMetrics.setMemorySize(mApfCapabilities.maximumApfProgramSize); + mApfSessionInfoMetrics.setVersion(mApfVersionSupported); + mApfSessionInfoMetrics.setMemorySize(mApfRamSize); mApfSessionInfoMetrics.setApfSessionDurationSeconds( (int) (sessionDurationMs / DateUtils.SECOND_IN_MILLIS)); mApfSessionInfoMetrics.setNumOfTimesApfProgramUpdated(mNumProgramUpdates); @@ -2658,6 +2698,15 @@ installNewProgramLocked(); } + @Override + public boolean supportNdOffload() { + return shouldUseApfV6Generator() && mShouldHandleNdOffload; + } + + private boolean shouldUseApfV6Generator() { + return SdkLevel.isAtLeastV() && ApfV6Generator.supportsVersion(mApfVersionSupported); + } + /** * Add TCP keepalive ack packet filter. * This will add a filter to drop acks to the keepalive packet passed as an argument. @@ -2713,17 +2762,48 @@ } public synchronized void dump(IndentingPrintWriter pw) { - pw.println("Capabilities: " + mApfCapabilities); + pw.println(String.format( + "Capabilities: { apfVersionSupported: %d, maximumApfProgramSize: %d }", + mApfVersionSupported, mApfRamSize)); pw.println("InstallableProgramSizeClamp: " + mInstallableProgramSizeClamp); 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); + pw.println("Interface MAC address: " + MacAddress.fromBytes(mHardwareAddress)); + pw.println("Multicast MAC addresses: "); + pw.increaseIndent(); + for (byte[] addr : mDependencies.getEtherMulticastAddresses(mInterfaceParams.name)) { + pw.println(MacAddress.fromBytes(addr)); + } + pw.decreaseIndent(); try { pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress()); - pw.println("IPv6 addresses: "); + pw.println("IPv6 non-tentative addresses: "); pw.increaseIndent(); - for (Inet6Address addr: mIPv6NonTentativeAddresses) { + for (Inet6Address addr : mIPv6NonTentativeAddresses) { + pw.println(addr.getHostAddress()); + } + pw.decreaseIndent(); + pw.println("IPv6 tentative addresses: "); + pw.increaseIndent(); + for (Inet6Address addr : mIPv6TentativeAddresses) { + pw.println(addr.getHostAddress()); + } + pw.decreaseIndent(); + pw.println("IPv6 anycast addresses:"); + pw.increaseIndent(); + final List<Inet6Address> anycastAddrs = + ProcfsParsingUtils.getAnycast6Addresses(mInterfaceParams.name); + for (Inet6Address addr : anycastAddrs) { + pw.println(addr.getHostAddress()); + } + pw.decreaseIndent(); + pw.println("IPv6 multicast addresses:"); + pw.increaseIndent(); + final List<Inet6Address> multicastAddrs = + ProcfsParsingUtils.getIpv6MulticastAddresses(mInterfaceParams.name); + for (Inet6Address addr : multicastAddrs) { pw.println(addr.getHostAddress()); } pw.decreaseIndent(); @@ -2796,7 +2876,7 @@ pw.println("APF packet counters: "); pw.increaseIndent(); - if (!hasDataAccess(mApfCapabilities)) { + if (!hasDataAccess(mApfVersionSupported)) { pw.println("APF counters not supported"); } else if (mDataSnapshot == null) { pw.println("No last snapshot."); @@ -2810,10 +2890,17 @@ 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."); + final Set<Counter> skipCheckCounters = Set.of(FILTER_AGE_SECONDS, + FILTER_AGE_16384THS); + if (!skipCheckCounters.contains(c)) { + // If the counter's value decreases, it may have been cleaned up or there + // may be a bug. + long oldValue = mApfCounterTracker.getCounters().getOrDefault(c, 0L); + if (value < oldValue) { + Log.e(TAG, String.format( + "Apf Counter: %s unexpectedly decreased. oldValue: %d. " + + "newValue: %d", c.toString(), oldValue, value)); + } } } } catch (ArrayIndexOutOfBoundsException e) {
diff --git a/src/android/net/apf/ApfV4Generator.java b/src/android/net/apf/ApfV4Generator.java index a41f033..f9918b2 100644 --- a/src/android/net/apf/ApfV4Generator.java +++ b/src/android/net/apf/ApfV4Generator.java
@@ -61,10 +61,10 @@ * the requested version is unsupported. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public ApfV4Generator(int version, boolean disableCounterRangeCheck) + public ApfV4Generator(int version, int ramSize, int clampSize, boolean disableCounterRangeCheck) throws IllegalInstructionException { // make sure mVersion is not greater than 4 when using this class - super(version > 4 ? 4 : version, disableCounterRangeCheck); + super(version > 4 ? 4 : version, ramSize, clampSize, disableCounterRangeCheck); mCountAndDropLabel = version > 2 ? COUNT_AND_DROP_LABEL : DROP_LABEL; mCountAndPassLabel = version > 2 ? COUNT_AND_PASS_LABEL : PASS_LABEL; } @@ -74,8 +74,9 @@ * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if * the requested version is unsupported. */ - public ApfV4Generator(int version) throws IllegalInstructionException { - this(version, false); + public ApfV4Generator(int version, int ramSize, int clampSize) + throws IllegalInstructionException { + this(version, ramSize, clampSize, false); } @Override @@ -202,20 +203,6 @@ } @Override - public ApfV4Generator addCountAndDropIfBytesAtR0Equal(byte[] bytes, - ApfCounterTracker.Counter cnt) throws IllegalInstructionException { - final String tgt = getUniqueLabel(); - return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt); - } - - @Override - public ApfV4Generator addCountAndPassIfBytesAtR0Equal(byte[] bytes, - ApfCounterTracker.Counter cnt) throws IllegalInstructionException { - final String tgt = getUniqueLabel(); - return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt); - } - - @Override public ApfV4Generator addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) {
diff --git a/src/android/net/apf/ApfV4GeneratorBase.java b/src/android/net/apf/ApfV4GeneratorBase.java index ced1d68..a00aa2f 100644 --- a/src/android/net/apf/ApfV4GeneratorBase.java +++ b/src/android/net/apf/ApfV4GeneratorBase.java
@@ -52,9 +52,9 @@ * the requested version is unsupported. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public ApfV4GeneratorBase(int version, boolean disableCounterRangeCheck) - throws IllegalInstructionException { - super(version, disableCounterRangeCheck); + public ApfV4GeneratorBase(int version, int ramSize, int clampSize, + boolean disableCounterRangeCheck) throws IllegalInstructionException { + super(version, ramSize, clampSize, disableCounterRangeCheck); requireApfVersion(APF_VERSION_2); } @@ -504,16 +504,23 @@ * bytes of the packet at an offset specified by register0 match {@code bytes}. * WARNING: may modify R1 */ - public abstract Type addCountAndDropIfBytesAtR0Equal(byte[] bytes, - ApfCounterTracker.Counter cnt) throws IllegalInstructionException; + public final Type addCountAndDropIfBytesAtR0Equal(byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + final String tgt = getUniqueLabel(); + return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt); + } + /** * Add instructions to the end of the program to increase counter and pass packet if the * bytes of the packet at an offset specified by register0 match {@code bytes}. * WARNING: may modify R1 */ - public abstract Type addCountAndPassIfBytesAtR0Equal(byte[] bytes, - ApfCounterTracker.Counter cnt) throws IllegalInstructionException; + public final Type addCountAndPassIfBytesAtR0Equal(byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + final String tgt = getUniqueLabel(); + return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt); + } /** * Add instructions to the end of the program to increase counter and pass packet if the
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java index d425907..f943bed 100644 --- a/src/android/net/apf/ApfV6Generator.java +++ b/src/android/net/apf/ApfV6Generator.java
@@ -35,14 +35,15 @@ /** * Creates an ApfV6Generator instance which emits instructions for APFv6. */ - public ApfV6Generator(int maximumApfProgramSize) throws IllegalInstructionException { - this(new byte[0], maximumApfProgramSize); + public ApfV6Generator(int version, int ramSize, int clampSize) + throws IllegalInstructionException { + this(new byte[0], version, ramSize, clampSize); } @Override void updateExceptionBufferSize(int programSize) throws IllegalInstructionException { mInstructions.get(1).updateExceptionBufferSize( - mMaximumApfProgramSize - ApfCounterTracker.Counter.totalSize() - programSize); + mRamSize - ApfCounterTracker.Counter.totalSize() - programSize); } /** @@ -50,9 +51,9 @@ * Initializes the data region with {@code bytes}. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public ApfV6Generator(byte[] bytes, int maximumApfProgramSize) + public ApfV6Generator(byte[] bytes, int version, int ramSize, int clampSize) throws IllegalInstructionException { - super(maximumApfProgramSize); + super(version, ramSize, clampSize); Objects.requireNonNull(bytes); addData(bytes); addExceptionBuffer(0);
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java index a9abed6..17629d1 100644 --- a/src/android/net/apf/ApfV6GeneratorBase.java +++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -40,17 +40,15 @@ public abstract class ApfV6GeneratorBase<Type extends ApfV6GeneratorBase<Type>> extends ApfV4GeneratorBase<Type> { - final int mMaximumApfProgramSize; - /** * Creates an ApfV6GeneratorBase instance which is able to emit instructions for the specified * {@code version} of the APF interpreter. Throws {@code IllegalInstructionException} if * the requested version is unsupported. * */ - public ApfV6GeneratorBase(int maximumApfProgramSize) throws IllegalInstructionException { - super(APF_VERSION_6, false); - this.mMaximumApfProgramSize = maximumApfProgramSize; + public ApfV6GeneratorBase(int version, int ramSize, int clampSize) + throws IllegalInstructionException { + super(version, ramSize, clampSize, false); } /** @@ -712,20 +710,6 @@ } @Override - public final Type addCountAndDropIfBytesAtR0Equal(byte[] bytes, - ApfCounterTracker.Counter cnt) throws IllegalInstructionException { - final String tgt = getUniqueLabel(); - return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt); - } - - @Override - public final Type addCountAndPassIfBytesAtR0Equal(byte[] bytes, - ApfCounterTracker.Counter cnt) throws IllegalInstructionException { - final String tgt = getUniqueLabel(); - return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt); - } - - @Override public Type addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) {
diff --git a/src/android/net/apf/BaseApfGenerator.java b/src/android/net/apf/BaseApfGenerator.java index 5552165..2eab5ab 100644 --- a/src/android/net/apf/BaseApfGenerator.java +++ b/src/android/net/apf/BaseApfGenerator.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull; +import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.ByteUtils; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.HexDump; @@ -39,9 +40,12 @@ */ public abstract class BaseApfGenerator { - public BaseApfGenerator(int mVersion, boolean mDisableCounterRangeCheck) { - this.mVersion = mVersion; - this.mDisableCounterRangeCheck = mDisableCounterRangeCheck; + public BaseApfGenerator(int version, int ramSize, int clampSize, + boolean disableCounterRangeCheck) { + mVersion = version; + mRamSize = ramSize; + mClampSize = clampSize; + mDisableCounterRangeCheck = disableCounterRangeCheck; } /** @@ -851,7 +855,8 @@ /** * Return a unique label string. */ - protected String getUniqueLabel() { + @VisibleForTesting + public String getUniqueLabel() { return "LABEL_" + mLabelCount++; } @@ -954,6 +959,8 @@ private final Instruction mDropLabel = new Instruction(Opcodes.LABEL); private final Instruction mPassLabel = new Instruction(Opcodes.LABEL); public final int mVersion; + public final int mRamSize; + public final int mClampSize; public boolean mGenerated; private final boolean mDisableCounterRangeCheck; }
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java index e4f709b..d50d5cb 100644 --- a/src/android/net/apf/LegacyApfFilter.java +++ b/src/android/net/apf/LegacyApfFilter.java
@@ -21,7 +21,6 @@ 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.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; @@ -126,7 +125,7 @@ * When APFv4 is supported, loads R1 with the offset of the specified counter. */ private void maybeSetupCounter(ApfV4Generator gen, Counter c) { - if (hasDataAccess(mApfCapabilities)) { + if (hasDataAccess(mApfVersionSupported)) { gen.addLoadImmediate(R1, c.offset()); } } @@ -214,7 +213,7 @@ .setZeroLifetimeRas(mZeroLifetimeRas) .setProgramUpdates(mProgramUpdates) .setDurationMs(nowMs - mStart) - .setMaxProgramSize(mApfCapabilities.maximumApfProgramSize) + .setMaxProgramSize(mMaximumApfProgramSize) .setProgramUpdatesAll(mNumProgramUpdates) .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast) .build(); @@ -306,7 +305,8 @@ ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN; - private final ApfCapabilities mApfCapabilities; + public final int mApfVersionSupported; + public final int mMaximumApfProgramSize; private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; private final IpConnectivityLog mMetricsLog; @@ -393,7 +393,8 @@ InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, ApfFilter.Clock clock) { - mApfCapabilities = config.apfCapabilities; + mApfVersionSupported = config.apfVersionSupported; + mMaximumApfProgramSize = config.apfRamSize; mIpClientCallback = ipClientCallback; mInterfaceParams = ifParams; mMulticastFilter = config.multicastFilter; @@ -408,7 +409,7 @@ mSessionStartMs = mClock.elapsedRealtime(); mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs; - if (hasDataAccess(mApfCapabilities)) { + if (hasDataAccess(mApfVersionSupported)) { mCountAndPassLabel = "countAndPass"; mCountAndDropLabel = "countAndDrop"; } else { @@ -494,8 +495,8 @@ // 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(mApfCapabilities)) { - byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize]; + if (mIsRunning && hasDataAccess(mApfVersionSupported)) { + byte[] zeroes = new byte[mMaximumApfProgramSize]; if (!mIpClientCallback.installPacketFilter(zeroes)) { sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); } @@ -1728,9 +1729,10 @@ @GuardedBy("this") protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. - ApfV4Generator gen = new ApfV4Generator(mApfCapabilities.apfVersionSupported); + ApfV4Generator gen = new ApfV4Generator(mApfVersionSupported, mMaximumApfProgramSize, + mMaximumApfProgramSize); - if (hasDataAccess(mApfCapabilities)) { + if (hasDataAccess(mApfVersionSupported)) { // Increment TOTAL_PACKETS maybeSetupCounter(gen, Counter.TOTAL_PACKETS); gen.addLoadData(R0, 0); // load counter @@ -1833,7 +1835,7 @@ 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(mApfCapabilities)) return; + 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. @@ -1868,8 +1870,8 @@ ArrayList<Ra> rasToFilter = new ArrayList<>(); final byte[] program; long programMinLifetime = Long.MAX_VALUE; - long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize; - if (hasDataAccess(mApfCapabilities)) { + long maximumApfProgramSize = mMaximumApfProgramSize; + if (hasDataAccess(mApfVersionSupported)) { // Reserve space for the counters. maximumApfProgramSize -= Counter.totalSize(); } @@ -2071,20 +2073,11 @@ InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics) { if (context == null || config == null || ifParams == null) return null; - ApfCapabilities apfCapabilities = config.apfCapabilities; - if (apfCapabilities == null) return null; - if (apfCapabilities.apfVersionSupported == 0) return null; - if (apfCapabilities.maximumApfProgramSize < 512) { - Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize); + if (!ApfV4Generator.supportsVersion(config.apfVersionSupported)) { return null; } - // For now only support generating programs for Ethernet frames. If this restriction is - // lifted: - // 1. the program generator will need its offsets adjusted. - // 2. the packet filter attached to our packet socket will need its offset adjusted. - if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null; - if (!ApfV4Generator.supportsVersion(apfCapabilities.apfVersionSupported)) { - Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); + if (config.apfRamSize < 512) { + Log.e(TAG, "Unacceptably small APF limit: " + config.apfRamSize); return null; } @@ -2108,8 +2101,8 @@ mIpClientRaInfoMetrics.statsWrite(); // Collect and send ApfSessionInfoMetrics. - mApfSessionInfoMetrics.setVersion(mApfCapabilities.apfVersionSupported); - mApfSessionInfoMetrics.setMemorySize(mApfCapabilities.maximumApfProgramSize); + mApfSessionInfoMetrics.setVersion(mApfVersionSupported); + mApfSessionInfoMetrics.setMemorySize(mMaximumApfProgramSize); mApfSessionInfoMetrics.setApfSessionDurationSeconds( (int) (sessionDurationMs / DateUtils.SECOND_IN_MILLIS)); mApfSessionInfoMetrics.setNumOfTimesApfProgramUpdated(mNumProgramUpdates); @@ -2249,7 +2242,9 @@ } public synchronized void dump(IndentingPrintWriter pw) { - pw.println("Capabilities: " + mApfCapabilities); + 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")); @@ -2325,7 +2320,7 @@ pw.println("APF packet counters: "); pw.increaseIndent(); - if (!hasDataAccess(mApfCapabilities)) { + if (!hasDataAccess(mApfVersionSupported)) { pw.println("APF counters not supported"); } else if (mDataSnapshot == null) { pw.println("No last snapshot.");
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index deaabac..7e21116 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -30,7 +30,9 @@ import static android.net.ip.IpReachabilityMonitor.nudEventTypeToInt; import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.stats.connectivity.NetworkQuirkEvent.QE_DHCP6_HEURISTIC_TRIGGERED; import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IPV6; import static android.system.OsConstants.IFA_F_NODAD; @@ -46,8 +48,8 @@ import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID; import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR; import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE; -import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_ARP_OFFLOAD_FORCE_DISABLE; -import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE; +import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_ARP_OFFLOAD; +import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_ND_OFFLOAD; import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION; import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION; @@ -597,7 +599,7 @@ static final String CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS = "ipclient_apf_counter_polling_interval_secs"; @VisibleForTesting - static final int DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS = 300; + static final int DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS = 1800; // Used to wait for the provisioning to complete eventually and then decide the target // network type, which gives the accurate hint to set DTIM multiplier. Per current IPv6 @@ -731,10 +733,10 @@ private final boolean mDhcp6PrefixDelegationEnabled; private final boolean mUseNewApfFilter; private final boolean mEnableIpClientIgnoreLowRaLifetime; - private final boolean mApfShouldHandleLightDoze; private final boolean mEnableApfPollingCounters; private final boolean mPopulateLinkAddressLifetime; private final boolean mApfShouldHandleArpOffload; + private final boolean mApfShouldHandleNdOffload; private InterfaceParams mInterfaceParams; @@ -981,11 +983,10 @@ mEnableIpClientIgnoreLowRaLifetime = SdkLevel.isAtLeastV() || mDependencies.isFeatureEnabled(context, IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION); - // Light doze mode status checking API is only available at T or later releases. - mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut( - mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE); mApfShouldHandleArpOffload = mDependencies.isFeatureNotChickenedOut( - mContext, APF_HANDLE_ARP_OFFLOAD_FORCE_DISABLE); + mContext, APF_HANDLE_ARP_OFFLOAD); + mApfShouldHandleNdOffload = mDependencies.isFeatureNotChickenedOut( + mContext, APF_HANDLE_ND_OFFLOAD); mPopulateLinkAddressLifetime = mDependencies.isFeatureEnabled(context, IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION); @@ -1022,10 +1023,15 @@ public void onClatInterfaceStateUpdate(boolean add) { getHandler().post(() -> { if (mHasSeenClatInterface == add) return; - // Clat interface information is spliced into LinkProperties by - // ConnectivityService, so it cannot be added to the LinkProperties - // here as those propagate back to ConnectivityService. - mCallback.setNeighborDiscoveryOffload(add ? false : true); + // If Apf is not supported or Apf doesn't support ND offload, then + // configure the vendor ND offload feature based on the Clat + // interface state. + if (mApfFilter == null || !mApfFilter.supportNdOffload()) { + // Clat interface information is spliced into LinkProperties by + // ConnectivityService, so it cannot be added to the LinkProperties + // here as those propagate back to ConnectivityService. + mCallback.setNeighborDiscoveryOffload(add ? false : true); + } mHasSeenClatInterface = add; if (mApfFilter != null) { mApfFilter.updateClatInterfaceState(add); @@ -1390,7 +1396,8 @@ pw.println(mTag + " APF dump:"); pw.increaseIndent(); if (apfFilter != null) { - if (apfCapabilities != null && apfFilter.hasDataAccess(apfCapabilities)) { + if (apfCapabilities != null && apfFilter.hasDataAccess( + apfCapabilities.apfVersionSupported)) { // Request a new snapshot, then wait for it. mApfDataSnapshotComplete.close(); mCallback.startReadPacketFilter("dumpsys"); @@ -2160,17 +2167,20 @@ // Returns false if we have lost provisioning, true otherwise. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); - // LinkProperties.equals just compares if the interface addresses are identical, - // it doesn't compare the LinkAddress objects, so it considers two LinkProperties - // objects are identical even with different address lifetime. However, we may want - // to notify the caller whenever the link address lifetime is updated, especially - // after we enable populating the deprecationTime/expirationTime fields. The caller - // can get the latest address lifetime from the onLinkPropertiesChange callback. + + // We need to call mApfFilter.setLinkProperties(newLp) every time there is a LinkAddress + // change because ApfFilter needs to know when addresses change from tentative to + // non-tentative. setLinkProperties() inside IpClient won't be called if the + // LinkProperties.equal() check returns true. The LinkProperties.equal() check does not + // currently take into account the LinkAddress flag change. + // It is OK to call mApfFilter.setLinkProperties() multiple times because if IP + // addresses are not updated, ApfFilter won't generate new program. + if (mApfFilter != null) { + mApfFilter.setLinkProperties(newLp); + } + if (Objects.equals(newLp, mLinkProperties)) { - if (!mPopulateLinkAddressLifetime) return true; - if (LinkPropertiesUtils.isIdenticalAllLinkAddresses(newLp, mLinkProperties)) { - return true; - } + return true; } // Set an alarm to wait for IPv6 autoconf via SLAAC to succeed after receiving an RA, @@ -2556,17 +2566,24 @@ @Nullable private AndroidPacketFilter maybeCreateApfFilter(final ApfCapabilities apfCaps) { ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); - apfConfig.apfCapabilities = apfCaps; - if (apfCaps != null && !SdkLevel.isAtLeastS()) { - // Due to potential OEM modifications in Android R, reconfigure - // apfVersionSupported using apfCapabilities.hasDataAccess() to ensure safe data - // region access within ApfFilter. - int apfVersionSupported = apfCaps.hasDataAccess() ? 3 : 2; - apfConfig.apfCapabilities = new ApfCapabilities(apfVersionSupported, - apfCaps.maximumApfProgramSize, apfCaps.apfPacketFormat); + if (apfCaps == null) { + return null; } - if (apfConfig.apfCapabilities != null && !SdkLevel.isAtLeastV() - && apfConfig.apfCapabilities.apfVersionSupported <= 4) { + // For now only support generating programs for Ethernet frames. If this restriction is + // lifted the program generator will need its offsets adjusted. + if (apfCaps.apfPacketFormat != ARPHRD_ETHER) return null; + if (SdkLevel.isAtLeastS()) { + apfConfig.apfVersionSupported = apfCaps.apfVersionSupported; + } else { + // In Android R, ApfCapabilities#hasDataAccess() can be modified by OEMs. The + // ApfFilter logic uses ApfCapabilities.apfVersionSupported to determine whether + // data region access is supported. Therefore, we need to recalculate + // ApfCapabilities.apfVersionSupported based on the return value of + // ApfCapabilities#hasDataAccess(). + apfConfig.apfVersionSupported = apfCaps.hasDataAccess() ? 3 : 2; + } + apfConfig.apfRamSize = apfCaps.maximumApfProgramSize; + if (!SdkLevel.isAtLeastV() && apfConfig.apfVersionSupported <= 4) { apfConfig.installableProgramSizeClamp = 1024; } apfConfig.multicastFilter = mMulticastFiltering; @@ -2593,8 +2610,8 @@ } else { apfConfig.acceptRaMinLft = 0; } - apfConfig.shouldHandleLightDoze = mApfShouldHandleLightDoze; apfConfig.shouldHandleArpOffload = mApfShouldHandleArpOffload; + apfConfig.shouldHandleNdOffload = mApfShouldHandleNdOffload; apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs; apfConfig.hasClatInterface = mHasSeenClatInterface; return mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams, @@ -3092,6 +3109,10 @@ // at the beginning. mHasSeenClatInterface = false; mApfFilter = maybeCreateApfFilter(mCurrentApfCapabilities); + // If Apf supports ND offload, then turn off the vendor ND offload feature. + if (mApfFilter != null && mApfFilter.supportNdOffload()) { + mCallback.setNeighborDiscoveryOffload(false); + } // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. if (mApfFilter == null) { @@ -3413,6 +3434,8 @@ if (!hasIpv6Address(mLinkProperties) && mLinkProperties.hasIpv6DefaultRoute()) { Log.d(TAG, "Network supports IPv6 but not autoconf, starting DHCPv6 PD"); + mNetworkQuirkMetrics.setEvent(QE_DHCP6_HEURISTIC_TRIGGERED); + mNetworkQuirkMetrics.statsWrite(); startDhcp6PrefixDelegation(); } break; @@ -3547,6 +3570,10 @@ final ApfCapabilities apfCapabilities = (ApfCapabilities) msg.obj; if (handleUpdateApfCapabilities(apfCapabilities)) { mApfFilter = maybeCreateApfFilter(apfCapabilities); + // If Apf supports ND offload, then turn off the vendor ND offload feature. + if (mApfFilter != null && mApfFilter.supportNdOffload()) { + mCallback.setNeighborDiscoveryOffload(false); + } } break;
diff --git a/src/android/net/util/RawSocketUtils.java b/src/android/net/util/RawSocketUtils.java new file mode 100644 index 0000000..a6c8a40 --- /dev/null +++ b/src/android/net/util/RawSocketUtils.java
@@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 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.util; + +import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOCK_RAW; + +import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_LENGTH; +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET; + +import android.annotation.RequiresPermission; +import android.content.Context; +import android.net.TetheringManager; +import android.system.Os; + +import androidx.annotation.NonNull; + +import com.android.internal.util.HexDump; + +import java.io.FileDescriptor; +import java.net.NetworkInterface; +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class RawSocketUtils { + // For isTetheredInterface, a quick Tethering event callback is expected + // since there's no start/stop Tethering involved. This timeout allows + // system messages to be handled, preventing flaky test results. + private static final int TETHERING_EVENT_CALLBACK_TIMEOUT_MS = 3000; + + /** + * Send a raw packet represents in Hex format to the downstream interface. + * <p> + * Note that the target interface is limited to tethering downstream + * for security considerations. + */ + @RequiresPermission(NETWORK_SETTINGS) + public static void sendRawPacketDownStream(@NonNull Context context, @NonNull String ifaceName, + @NonNull String packetInHex) throws Exception { + // 1. Verify Tethering Downstream Interface. + enforceTetheredInterface(context, ifaceName); + + // 2. Hex to Byte Array Conversion + final byte[] packetData = HexDump.hexStringToByteArray(packetInHex); + final byte[] destMac = Arrays.copyOfRange(packetData, ETHER_DST_ADDR_OFFSET, + ETHER_DST_ADDR_OFFSET + ETHER_ADDR_LEN); + final byte[] etherTypeBytes = Arrays.copyOfRange(packetData, ETHER_TYPE_OFFSET, + ETHER_TYPE_OFFSET + ETHER_TYPE_LENGTH); + final int etherType = ((etherTypeBytes[0] & 0xFF) << 8) | (etherTypeBytes[1] & 0xFF); + + // 3. Obtain Network Interface + final NetworkInterface iface = NetworkInterface.getByName(ifaceName); + if (iface == null) { + throw new IllegalArgumentException("Invalid network interface: " + ifaceName); + } + + // 4. Construct and Send Packet. + final SocketAddress addr = SocketUtils.makePacketSocketAddress( + etherType, + iface.getIndex(), + destMac + ); + final FileDescriptor sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0); + try { + Os.sendto(sock, packetData, 0, packetData.length, 0, addr); + } finally { + SocketUtils.closeSocket(sock); + } + } + + @RequiresPermission(NETWORK_SETTINGS) + private static void enforceTetheredInterface(@NonNull Context context, + @NonNull String interfaceName) + throws ExecutionException, InterruptedException, TimeoutException { + final TetheringManager tm = context.getSystemService(TetheringManager.class); + final CompletableFuture<List<String>> tetheredInterfaces = new CompletableFuture<>(); + final TetheringManager.TetheringEventCallback callback = + new TetheringManager.TetheringEventCallback() { + @Override + public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) { + tetheredInterfaces.complete(interfaces); + } + }; + tm.registerTetheringEventCallback(c -> c.run() /* executor */, callback); + final List<String> tetheredIfaces = tetheredInterfaces.get( + TETHERING_EVENT_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (!tetheredIfaces.contains(interfaceName)) { + throw new SecurityException("Only tethered interfaces " + tetheredIfaces + + " are expected, but got " + interfaceName); + } + } +}
diff --git a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java index d1776c9..c2c51f6 100644 --- a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java +++ b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
@@ -16,11 +16,115 @@ package com.android.networkstack.metrics; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_802_3_FRAME; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_ANYHOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_UNKNOWN; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_V6_ONLY; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_GARP_REPLY; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_ADDR; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_NET; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_KEEPALIVE_ACK; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_L2_BROADCAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_MULTICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NATT_KEEPALIVE; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_KEEPALIVE_ACK; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ROUTER_SOLICITATION; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS; +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; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_UNICAST; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NON_ICMP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_UNICAST_NON_ICMP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_MDNS; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_MLD; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_NON_IP_UNICAST; +import static android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS; +import static android.stats.connectivity.CounterName.CN_DROPPED_802_3_FRAME; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_NON_IPV4; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_OTHER_HOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REQUEST_ANYHOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REQUEST_REPLIED; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_UNKNOWN; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_V6_ONLY; +import static android.stats.connectivity.CounterName.CN_DROPPED_ETHERTYPE_NOT_ALLOWED; +import static android.stats.connectivity.CounterName.CN_DROPPED_ETH_BROADCAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_GARP_REPLY; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_BROADCAST_NET; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_KEEPALIVE_ACK; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_L2_BROADCAST; +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_IPV6_KEEPALIVE_ACK; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_NA; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_PING; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_INVALID; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_OTHER_HOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_REPLIED_NON_DAD; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_ROUTER_SOLICITATION; +import static android.stats.connectivity.CounterName.CN_DROPPED_MDNS; +import static android.stats.connectivity.CounterName.CN_DROPPED_RA; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP_BROADCAST_REPLY; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP_REQUEST; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP_UNICAST_REPLY; +import static android.stats.connectivity.CounterName.CN_PASSED_DHCP; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV4; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV4_FROM_DHCPV4_SERVER; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV4_UNICAST; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_ICMP; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NON_ICMP; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_DAD; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_NO_ADDRESS; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_NO_SLLA_OPTION; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_TENTATIVE; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP; +import static android.stats.connectivity.CounterName.CN_PASSED_MDNS; +import static android.stats.connectivity.CounterName.CN_PASSED_MLD; +import static android.stats.connectivity.CounterName.CN_PASSED_NON_IP_UNICAST; +import static android.stats.connectivity.CounterName.CN_TOTAL_PACKETS; +import static android.stats.connectivity.CounterName.CN_UNKNOWN; + import android.net.apf.ApfCounterTracker.Counter; import android.stats.connectivity.CounterName; import androidx.annotation.VisibleForTesting; +import java.util.EnumMap; +import java.util.Map; + /** * Class to record the network stack ApfSessionInfo metrics into statsd. * @@ -31,6 +135,65 @@ public class ApfSessionInfoMetrics { // Define the maximum size of the counter list public static final int MAX_NUM_OF_COUNTERS = Counter.class.getEnumConstants().length - 1; + private static final EnumMap<Counter, CounterName> apfCounterMetricsMap = new EnumMap<>( + Map.ofEntries( + Map.entry(TOTAL_PACKETS, CN_TOTAL_PACKETS), + // 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), + Map.entry(PASSED_IPV4_UNICAST, CN_PASSED_IPV4_UNICAST), + Map.entry(PASSED_IPV6_ICMP, CN_PASSED_IPV6_ICMP), + Map.entry(PASSED_IPV6_NON_ICMP, CN_PASSED_IPV6_NON_ICMP), + Map.entry(PASSED_IPV6_NS_DAD, CN_PASSED_IPV6_NS_DAD), + Map.entry(PASSED_IPV6_NS_NO_ADDRESS, CN_PASSED_IPV6_NS_NO_ADDRESS), + Map.entry(PASSED_IPV6_NS_NO_SLLA_OPTION, CN_PASSED_IPV6_NS_NO_SLLA_OPTION), + Map.entry(PASSED_IPV6_NS_TENTATIVE, CN_PASSED_IPV6_NS_TENTATIVE), + Map.entry(PASSED_IPV6_UNICAST_NON_ICMP, CN_PASSED_IPV6_UNICAST_NON_ICMP), + Map.entry(PASSED_NON_IP_UNICAST, CN_PASSED_NON_IP_UNICAST), + Map.entry(PASSED_MDNS, CN_PASSED_MDNS), + Map.entry(PASSED_MLD, CN_PASSED_MLD), + Map.entry(DROPPED_ETH_BROADCAST, CN_DROPPED_ETH_BROADCAST), + Map.entry(DROPPED_RA, CN_DROPPED_RA), + Map.entry(DROPPED_IPV4_L2_BROADCAST, CN_DROPPED_IPV4_L2_BROADCAST), + Map.entry(DROPPED_IPV4_BROADCAST_ADDR, CN_DROPPED_IPV4_BROADCAST_ADDR), + Map.entry(DROPPED_IPV4_BROADCAST_NET, CN_DROPPED_IPV4_BROADCAST_NET), + Map.entry(DROPPED_IPV4_MULTICAST, CN_DROPPED_IPV4_MULTICAST), + Map.entry(DROPPED_IPV4_NON_DHCP4, CN_DROPPED_IPV4_NON_DHCP4), + Map.entry(DROPPED_IPV6_ROUTER_SOLICITATION, CN_DROPPED_IPV6_ROUTER_SOLICITATION), + Map.entry(DROPPED_IPV6_MULTICAST_NA, CN_DROPPED_IPV6_MULTICAST_NA), + Map.entry(DROPPED_IPV6_MULTICAST, CN_DROPPED_IPV6_MULTICAST), + Map.entry(DROPPED_IPV6_MULTICAST_PING, CN_DROPPED_IPV6_MULTICAST_PING), + Map.entry(DROPPED_IPV6_NON_ICMP_MULTICAST, CN_DROPPED_IPV6_NON_ICMP_MULTICAST), + Map.entry(DROPPED_IPV6_NS_INVALID, CN_DROPPED_IPV6_NS_INVALID), + Map.entry(DROPPED_IPV6_NS_OTHER_HOST, CN_DROPPED_IPV6_NS_OTHER_HOST), + Map.entry(DROPPED_IPV6_NS_REPLIED_NON_DAD, CN_DROPPED_IPV6_NS_REPLIED_NON_DAD), + Map.entry(DROPPED_802_3_FRAME, CN_DROPPED_802_3_FRAME), + Map.entry(DROPPED_ETHERTYPE_NOT_ALLOWED, CN_DROPPED_ETHERTYPE_NOT_ALLOWED), + Map.entry(DROPPED_IPV4_KEEPALIVE_ACK, CN_DROPPED_IPV4_KEEPALIVE_ACK), + 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_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), + Map.entry(DROPPED_ARP_REQUEST_ANYHOST, CN_DROPPED_ARP_REQUEST_ANYHOST), + Map.entry(DROPPED_ARP_REQUEST_REPLIED, CN_DROPPED_ARP_REQUEST_REPLIED), + Map.entry(DROPPED_ARP_UNKNOWN, CN_DROPPED_ARP_UNKNOWN), + Map.entry(DROPPED_ARP_V6_ONLY, CN_DROPPED_ARP_V6_ONLY), + Map.entry(DROPPED_GARP_REPLY, CN_DROPPED_GARP_REPLY) + ) + ); private final ApfSessionInfoReported.Builder mStatsBuilder = ApfSessionInfoReported.newBuilder(); private final ApfCounterList.Builder mApfCounterListBuilder = ApfCounterList.newBuilder(); @@ -104,85 +267,6 @@ */ @VisibleForTesting public static CounterName apfFilterCounterToEnum(final Counter counter) { - switch(counter) { - case TOTAL_PACKETS: - return CounterName.CN_TOTAL_PACKETS; - case PASSED_ARP: - return CounterName.CN_PASSED_ARP; - case PASSED_DHCP: - return CounterName.CN_PASSED_DHCP; - case PASSED_IPV4: - return CounterName.CN_PASSED_IPV4; - case PASSED_IPV6_NON_ICMP: - return CounterName.CN_PASSED_IPV6_NON_ICMP; - case PASSED_IPV4_UNICAST: - return CounterName.CN_PASSED_IPV4_UNICAST; - case PASSED_IPV6_ICMP: - return CounterName.CN_PASSED_IPV6_ICMP; - case PASSED_IPV6_UNICAST_NON_ICMP: - return CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP; - // PASSED_ARP_NON_IPV4 and PASSED_ARP_UNKNOWN were deprecated in ApfFilter: - // PASSED_ARP_NON_IPV4 ==> DROPPED_ARP_NON_IPV4 - // PASSED_ARP_UNKNOWN ==> DROPPED_ARP_UNKNOWN - // They are not supported in the metrics. - case PASSED_ARP_NON_IPV4: - case PASSED_ARP_UNKNOWN: - return CounterName.CN_UNKNOWN; - case PASSED_ARP_UNICAST_REPLY: - return CounterName.CN_PASSED_ARP_UNICAST_REPLY; - case PASSED_NON_IP_UNICAST: - return CounterName.CN_PASSED_NON_IP_UNICAST; - case PASSED_MDNS: - return CounterName.CN_PASSED_MDNS; - case DROPPED_ETH_BROADCAST: - return CounterName.CN_DROPPED_ETH_BROADCAST; - case DROPPED_RA: - return CounterName.CN_DROPPED_RA; - case DROPPED_GARP_REPLY: - return CounterName.CN_DROPPED_GARP_REPLY; - case DROPPED_ARP_OTHER_HOST: - return CounterName.CN_DROPPED_ARP_OTHER_HOST; - case DROPPED_IPV4_L2_BROADCAST: - return CounterName.CN_DROPPED_IPV4_L2_BROADCAST; - case DROPPED_IPV4_BROADCAST_ADDR: - return CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR; - case DROPPED_IPV4_BROADCAST_NET: - return CounterName.CN_DROPPED_IPV4_BROADCAST_NET; - case DROPPED_IPV4_MULTICAST: - return CounterName.CN_DROPPED_IPV4_MULTICAST; - case DROPPED_IPV6_ROUTER_SOLICITATION: - return CounterName.CN_DROPPED_IPV6_ROUTER_SOLICITATION; - case DROPPED_IPV6_MULTICAST_NA: - return CounterName.CN_DROPPED_IPV6_MULTICAST_NA; - case DROPPED_IPV6_MULTICAST: - return CounterName.CN_DROPPED_IPV6_MULTICAST; - case DROPPED_IPV6_MULTICAST_PING: - return CounterName.CN_DROPPED_IPV6_MULTICAST_PING; - case DROPPED_IPV6_NON_ICMP_MULTICAST: - return CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST; - case DROPPED_802_3_FRAME: - return CounterName.CN_DROPPED_802_3_FRAME; - case DROPPED_ETHERTYPE_NOT_ALLOWED: - return CounterName.CN_DROPPED_ETHERTYPE_DENYLISTED; - case DROPPED_ARP_REPLY_SPA_NO_HOST: - return CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST; - case DROPPED_IPV4_KEEPALIVE_ACK: - return CounterName.CN_DROPPED_IPV4_KEEPALIVE_ACK; - case DROPPED_IPV6_KEEPALIVE_ACK: - return CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK; - case DROPPED_IPV4_NATT_KEEPALIVE: - return CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE; - case DROPPED_MDNS: - return CounterName.CN_DROPPED_MDNS; - case DROPPED_IPV4_TCP_PORT7_UNICAST: - // TODO: Not supported yet in the metrics backend. - return CounterName.CN_UNKNOWN; - case DROPPED_ARP_NON_IPV4: - return CounterName.CN_DROPPED_ARP_NON_IPV4; - case DROPPED_ARP_UNKNOWN: - return CounterName.CN_DROPPED_ARP_UNKNOWN; - default: - return CounterName.CN_UNKNOWN; - } + return apfCounterMetricsMap.getOrDefault(counter, CN_UNKNOWN); } }
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index ac2832b..33a8308 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -274,13 +274,12 @@ public static final String IPCLIENT_DHCPV6_PD_PREFERRED_FLAG_VERSION = "ipclient_dhcpv6_pd_preferred_flag_version"; - /**** BEGIN Feature Kill Switch Flags ****/ - /** - * Kill switch flag to disable the feature of handle light doze mode in Apf. + * Experiment flag to enable Discovery of Designated Resolvers (DDR). */ - public static final String APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE = - "apf_handle_light_doze_force_disable"; + public static final String DNS_DDR_VERSION = "dns_ddr_version"; + + /**** BEGIN Feature Kill Switch Flags ****/ /** * Kill switch flag to disable the feature of skipping Tcp socket info polling when light @@ -302,9 +301,15 @@ /** * Kill switch flag to disable the feature of handle arp offload in Apf. + * Warning: the following flag String is incorrect. The feature that is not chickened out is + * "ARP offload" not "ARP offload force disabled". */ - public static final String APF_HANDLE_ARP_OFFLOAD_FORCE_DISABLE = - "apf_handle_arp_offload_force_disable"; + public static final String APF_HANDLE_ARP_OFFLOAD = "apf_handle_arp_offload_force_disable"; + + /** + * Kill switch flag to disable the feature of handle nd offload in Apf. + */ + public static final String APF_HANDLE_ND_OFFLOAD = "apf_handle_nd_offload"; static { System.loadLibrary("networkstackutilsjni");
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java index aa8f3fa..4d10c3b 100644 --- a/src/com/android/server/NetworkStackService.java +++ b/src/com/android/server/NetworkStackService.java
@@ -19,6 +19,7 @@ import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; +import static android.net.util.RawSocketUtils.sendRawPacketDownStream; import static com.android.net.module.util.DeviceConfigUtils.getResBooleanConfig; import static com.android.net.module.util.FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED; @@ -549,6 +550,23 @@ mContext.getSystemService(ConnectivityManager.class); pw.println(cm.isUidNetworkingBlocked(uid, metered /* isNetworkMetered */)); return 0; + case "send-raw-packet-downstream": { + // Usage : cmd network_stack send-raw-packet-downstream + // <interface> <packet-in-hex> + // If no argument, get and display the usage help. + if (getRemainingArgsCount() != 2) { + onHelp(); + throw new IllegalArgumentException("Incorrect number of arguments"); + } + final String iface = getNextArg(); + final String packetInHex = getNextArg(); + try { + sendRawPacketDownStream(mContext, iface, packetInHex); + } catch (Exception e) { + throw new RuntimeException(e); + } + return 0; + } case "apf": // Usage: cmd network_stack apf <iface> <cmd> final String iface = getNextArg(); @@ -585,6 +603,12 @@ pw.println(" Get whether the networking is blocked for given uid and metered."); pw.println(" <uid>: The target uid."); pw.println(" <metered>: [true|false], Whether the target network is metered."); + pw.println(" send-raw-packet-downstream <interface> <packet-in-hex>"); + pw.println(" Send raw packet for testing purpose."); + pw.println(" <interface>: Target interface name, note that this is limited"); + pw.println(" to tethering downstream for security considerations."); + pw.println(" <packet_in_hex>: A valid hexadecimal representation of "); + pw.println(" a packet starting from L2 header."); pw.println(" apf <iface> <cmd>"); pw.println(" APF utility commands for integration tests."); pw.println(" <iface>: the network interface the provided command operates on.");
diff --git a/src/com/android/server/connectivity/DdrTracker.java b/src/com/android/server/connectivity/DdrTracker.java new file mode 100644 index 0000000..af86914 --- /dev/null +++ b/src/com/android/server/connectivity/DdrTracker.java
@@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 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 com.android.server.connectivity; + +import static com.android.net.module.util.ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OFF; +import static com.android.net.module.util.ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static com.android.net.module.util.ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.net.shared.PrivateDnsConfig; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A class to perform DDR on a given network (to be implemented). + * + */ +class DdrTracker { + @IntDef(prefix = { "PRIVATE_DNS_MODE_" }, value = { + PRIVATE_DNS_MODE_OFF, + PRIVATE_DNS_MODE_OPPORTUNISTIC, + PRIVATE_DNS_MODE_PROVIDER_HOSTNAME + }) + @Retention(RetentionPolicy.SOURCE) + private @interface PrivateDnsMode {} + + // Stores the DNS information that is synced with current DNS configuration. + @NonNull + private DnsInfo mDnsInfo; + + DdrTracker() { + mDnsInfo = new DnsInfo(new PrivateDnsConfig(false /* useTls */)); + } + + /** + * If the private DNS settings on the network has changed, this function updates + * the DnsInfo and returns true; otherwise, the DnsInfo remains the same and this function + * returns false. + */ + boolean notifyPrivateDnsSettingsChanged(@NonNull PrivateDnsConfig cfg) { + if (arePrivateDnsSettingsEquals(cfg, mDnsInfo.cfg)) return false; + + mDnsInfo = new DnsInfo(cfg); + return true; + } + + @PrivateDnsMode int getPrivateDnsMode() { + return mDnsInfo.cfg.mode; + } + + // Returns a non-empty string (strict mode) or an empty string (off/opportunistic mode) . + @VisibleForTesting + @NonNull + String getStrictModeHostname() { + return mDnsInfo.cfg.hostname; + } + + @VisibleForTesting + private static boolean arePrivateDnsSettingsEquals(@NonNull PrivateDnsConfig a, + @NonNull PrivateDnsConfig b) { + return a.mode == b.mode && TextUtils.equals(a.hostname, b.hostname); + } + + /** + * A class to store current DNS configuration. Only the information relevant to DDR is stored. + * 1. Private DNS setting. + * 2. A list of Unencrypted DNS servers (to be implemented) + */ + private static class DnsInfo { + @NonNull + public final PrivateDnsConfig cfg; + + DnsInfo(@NonNull PrivateDnsConfig cfg) { + this.cfg = cfg; + } + } +}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index 895fc54..05103d9 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -462,6 +462,9 @@ @VisibleForTesting static final int MAX_PROBE_THREAD_POOL_SIZE = 5; private String mPrivateDnsProviderHostname = ""; + private final boolean mDdrEnabled; + @NonNull + private final DdrTracker mDdrTracker; private final Context mContext; private final INetworkMonitorCallbacks mCallback; @@ -679,6 +682,7 @@ context, NetworkStackUtils.REEVALUATE_WHEN_RESUME); mAsyncPrivdnsResolutionEnabled = deps.isFeatureEnabled(context, NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION); + mDdrEnabled = deps.isFeatureEnabled(context, NetworkStackUtils.DNS_DDR_VERSION); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalFallbackSpecs = @@ -714,6 +718,8 @@ mLinkProperties = new LinkProperties(); mNetworkCapabilities = new NetworkCapabilities(null); mNetworkAgentConfig = NetworkAgentConfigShimImpl.newInstance(null); + + mDdrTracker = new DdrTracker(); } /** @@ -1084,6 +1090,9 @@ case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj; final TcpSocketTracker tst = getTcpSocketTracker(); + if (mDdrEnabled) { + mDdrTracker.notifyPrivateDnsSettingsChanged(cfg); + } if (!isPrivateDnsValidationRequired() || !cfg.inStrictMode()) { // No DNS resolution required. //
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java index 84bf7e6..8c81de5 100644 --- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -2855,17 +2855,6 @@ (byte) 0x06, data); } - private void assertDhcpResultsParcelable(final DhcpResultsParcelable lease) { - assertNotNull(lease); - assertEquals(CLIENT_ADDR, lease.baseConfiguration.getIpAddress().getAddress()); - assertEquals(SERVER_ADDR, lease.baseConfiguration.getGateway()); - assertEquals(1, lease.baseConfiguration.getDnsServers().size()); - assertTrue(lease.baseConfiguration.getDnsServers().contains(SERVER_ADDR)); - assertEquals(SERVER_ADDR, InetAddresses.parseNumericAddress(lease.serverAddress)); - assertEquals(TEST_DEFAULT_MTU, lease.mtu); - assertEquals(TEST_LEASE_DURATION_S, lease.leaseDuration); - } - private void doUpstreamHotspotDetectionTest(final int id, final String displayName, final String ssid, final byte[] oui, final byte type, final byte[] data, final boolean expectMetered) throws Exception { @@ -2884,7 +2873,13 @@ ArgumentCaptor.forClass(DhcpResultsParcelable.class); verify(mCb, timeout(TEST_TIMEOUT_MS)).onNewDhcpResults(captor.capture()); final DhcpResultsParcelable lease = captor.getValue(); - assertDhcpResultsParcelable(lease); + assertNotNull(lease); + assertEquals(CLIENT_ADDR, lease.baseConfiguration.getIpAddress().getAddress()); + assertEquals(SERVER_ADDR, lease.baseConfiguration.getGateway()); + assertEquals(1, lease.baseConfiguration.getDnsServers().size()); + assertTrue(lease.baseConfiguration.getDnsServers().contains(SERVER_ADDR)); + assertEquals(SERVER_ADDR, InetAddresses.parseNumericAddress(lease.serverAddress)); + assertEquals(TEST_DEFAULT_MTU, lease.mtu); if (expectMetered) { assertEquals(lease.vendorInfo, DhcpPacket.VENDOR_INFO_ANDROID_METERED); @@ -5874,7 +5869,7 @@ final ProvisioningConfiguration cfg = new ProvisioningConfiguration.Builder() .withoutIPv6() .build(); - setDeviceConfigProperty(CONFIG_MINIMUM_LEASE, 5 /* default minimum lease */); + setDeviceConfigProperty(CONFIG_MINIMUM_LEASE, 5/* default minimum lease */); startIpClientProvisioning(cfg); handleDhcpPackets(true /* isSuccessLease */, 4 /* lease duration */, false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, @@ -5889,8 +5884,6 @@ sendArpReply(request.senderHwAddress.toByteArray() /* dst */, ROUTER_MAC_BYTES /* srcMac */, request.senderIp /* target IP */, SERVER_ADDR /* sender IP */); - clearInvocations(mCb); - // Then client sends unicast DHCPREQUEST to extend the IPv4 address lifetime, and we reply // with DHCPACK to refresh the DHCP lease. final DhcpPacket packet = getNextDhcpPacket(); @@ -5900,32 +5893,12 @@ TEST_LEASE_DURATION_S, (short) TEST_DEFAULT_MTU, false /* rapidCommit */, null /* captivePortalApiUrl */)); - // The IPv4 link address lifetime should be also updated after a success DHCP renew, check - // that we should never see provisioning failure. - verify(mCb, after(100).never()).onProvisioningFailure(any()); - - final ArgumentCaptor<DhcpResultsParcelable> dhcpResultsCaptor = - ArgumentCaptor.forClass(DhcpResultsParcelable.class); - verify(mCb, timeout(TEST_TIMEOUT_MS)).onNewDhcpResults(dhcpResultsCaptor.capture()); - final DhcpResultsParcelable lease = dhcpResultsCaptor.getValue(); - assertDhcpResultsParcelable(lease); - - // Check if the IPv4 address lifetime has updated along with a success DHCP renew. - verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat(x -> { - for (LinkAddress la : x.getLinkAddresses()) { - if (la.isIpv4()) { - final long now = SystemClock.elapsedRealtime(); - final long when = now + 3600 * 1000; - return (la.getDeprecationTime() != LinkAddress.LIFETIME_UNKNOWN) - && (la.getExpirationTime() != LinkAddress.LIFETIME_UNKNOWN) - && (la.getDeprecationTime() < when + TEST_LIFETIME_TOLERANCE_MS) - && (la.getDeprecationTime() > when - TEST_LIFETIME_TOLERANCE_MS) - && (la.getExpirationTime() < when + TEST_LIFETIME_TOLERANCE_MS) - && (la.getExpirationTime() > when - TEST_LIFETIME_TOLERANCE_MS); - } - } - return false; - })); + // Once the IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION flag is enabled, the IP + // lease will be refreshed as well as the link address lifetime by transiting to + // ConfiguringInterfaceState, where IpClient sends a new RTM_NEWADDR message to kernel + // to update the IPv4 address, therefore, we should never see provisioning failure any + // more. + verify(mCb, never()).onProvisioningFailure(any()); } private void doDhcpHostnameSettingTest(int hostnameSetting,
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 91e94a8..e26ea28 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp
@@ -32,7 +32,7 @@ "kotlin-reflect", "mockito-target-extended-minus-junit4", "net-tests-utils", - //"net-utils-framework-common", + "net-utils-framework-common", "testables", ], libs: [
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt new file mode 100644 index 0000000..938bf25 --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -0,0 +1,1197 @@ +/* + * Copyright (C) 2024 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 android.content.Context +import android.net.LinkAddress +import android.net.LinkProperties +import android.net.MacAddress +import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED +import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4 +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD +import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4 +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE +import android.net.apf.ApfFilter.Dependencies +import android.net.apf.ApfTestHelpers.Companion.verifyProgramRun +import android.net.apf.BaseApfGenerator.APF_VERSION_3 +import android.net.apf.BaseApfGenerator.APF_VERSION_6 +import android.net.ip.IpClient.IpClientCallbacksWrapper +import android.os.Build +import android.os.SystemClock +import android.system.OsConstants.IFA_F_TENTATIVE +import androidx.test.filters.SmallTest +import com.android.internal.annotations.GuardedBy +import com.android.net.module.util.HexDump +import com.android.net.module.util.InterfaceParams +import com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN +import com.android.net.module.util.NetworkStackConstants.ARP_REPLY +import com.android.net.module.util.NetworkStackConstants.ARP_REQUEST +import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN +import com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN +import com.android.net.module.util.NetworkStackConstants.ICMPV6_NS_HEADER_LEN +import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN +import com.android.net.module.util.arp.ArpPacket +import com.android.networkstack.metrics.NetworkQuirkMetrics +import com.android.networkstack.packets.NeighborAdvertisement +import com.android.networkstack.packets.NeighborSolicitation +import com.android.networkstack.util.NetworkStackUtils +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.quitResources +import java.net.Inet6Address +import java.net.InetAddress +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations +import org.mockito.invocation.InvocationOnMock + +/** + * Test for APF filter. + */ +@DevSdkIgnoreRunner.MonitorThreadLeak +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +class ApfFilterTest { + companion object { + private const val THREAD_QUIT_MAX_RETRY_COUNT = 3 + } + + @get:Rule + val ignoreRule = DevSdkIgnoreRule() + + @Mock + private lateinit var context: Context + + @Mock private lateinit var metrics: NetworkQuirkMetrics + + @Mock private lateinit var dependencies: Dependencies + + @Mock private lateinit var ipClientCallback: IpClientCallbacksWrapper + + @GuardedBy("mApfFilterCreated") + private val mApfFilterCreated = ArrayList<AndroidPacketFilter>() + private val loInterfaceParams = InterfaceParams.getByName("lo") + private val ifParams = + InterfaceParams( + "lo", + loInterfaceParams.index, + MacAddress.fromBytes(byteArrayOf(2, 3, 4, 5, 6, 7)), + loInterfaceParams.defaultMtu + ) + private val hostIpv4Address = byteArrayOf(10, 0, 0, 1) + private val senderIpv4Address = byteArrayOf(10, 0, 0, 2) + private val arpBroadcastMacAddress = intArrayOf(0xff, 0xff, 0xff, 0xff, 0xff, 0xff) + .map { it.toByte() }.toByteArray() + private val senderMacAddress = intArrayOf(0x02, 0x22, 0x33, 0x44, 0x55, 0x66) + .map { it.toByte() }.toByteArray() + private val senderIpv6Address = + // 2001::200:1a:1122:3344 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x11, 0x22, 0x33, 0x44) + .map{ it.toByte() }.toByteArray() + private val hostIpv6Addresses = listOf( + // 2001::200:1a:3344:1122 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x33, 0x44, 0x11, 0x22) + .map{ it.toByte() }.toByteArray(), + // 2001::100:1b:4455:6677 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x44, 0x55, 0x66, 0x77) + .map{ it.toByte() }.toByteArray() + ) + private val hostIpv6TentativeAddresses = listOf( + // 2001::200:1a:1234:5678 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x12, 0x34, 0x56, 0x78) + .map{ it.toByte() }.toByteArray(), + // 2001::100:1b:1234:5678 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x12, 0x34, 0x56, 0x78) + .map{ it.toByte() }.toByteArray() + ) + private val hostAnycast6Addresses = listOf( + // 2001::100:1b:aabb:ccdd + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0xaa, 0xbb, 0xcc, 0xdd) + .map{ it.toByte() }.toByteArray() + ) + private val hostMulticastMacAddresses = listOf( + // 33:33:00:00:00:01 + intArrayOf(0x33, 0x33, 0, 0, 0, 1).map { it.toByte() }.toByteArray(), + // 33:33:ff:44:11:22 + intArrayOf(0x33, 0x33, 0xff, 0x44, 0x11, 0x22).map { it.toByte() }.toByteArray(), + // 33:33:ff:55:66:77 + intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(), + // 33:33:ff:bb:cc:dd + intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(), + ) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + // mock anycast6 address from /proc/net/anycast6 + `when`(dependencies.getAnycast6Addresses(any())).thenReturn(hostAnycast6Addresses) + + // mock ether multicast mac address from /proc/net/dev_mcast + `when`(dependencies.getEtherMulticastAddresses(any())).thenReturn(hostMulticastMacAddresses) + + // mock nd traffic class from /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass + `when`(dependencies.getNdTrafficClass(any())).thenReturn(0) + doAnswer { invocation: InvocationOnMock -> + synchronized(mApfFilterCreated) { + mApfFilterCreated.add(invocation.getArgument(0)) + } + }.`when`(dependencies).onApfFilterCreated(any()) + `when`(dependencies.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime()) + } + + private fun shutdownApfFilters() { + quitResources(THREAD_QUIT_MAX_RETRY_COUNT, { + synchronized(mApfFilterCreated) { + val ret = ArrayList(mApfFilterCreated) + mApfFilterCreated.clear() + return@quitResources ret + } + }, { apf: AndroidPacketFilter -> + apf.shutdown() + }) + + synchronized(mApfFilterCreated) { + assertEquals( + 0, + mApfFilterCreated.size.toLong(), + "ApfFilters did not fully shutdown." + ) + } + } + + @After + fun tearDown() { + shutdownApfFilters() + Mockito.framework().clearInlineMocks() + ApfJniUtils.resetTransmittedPacketMemory() + } + + private fun getDefaultConfig(apfVersion: Int = APF_VERSION_6): ApfFilter.ApfConfiguration { + val config = ApfFilter.ApfConfiguration() + config.apfVersionSupported = apfVersion + // 4K is the highly recommended value in APFv6 for vendor + config.apfRamSize = 4096 + config.multicastFilter = false + config.ieee802_3Filter = false + config.ethTypeBlackList = IntArray(0) + config.shouldHandleArpOffload = true + config.shouldHandleNdOffload = true + return config + } + + private fun getApfFilter( + apfCfg: ApfFilter.ApfConfiguration = getDefaultConfig(APF_VERSION_6) + ): ApfFilter { + return ApfFilter( + context, + apfCfg, + ifParams, + ipClientCallback, + metrics, + dependencies + ) + } + + private fun doTestEtherTypeAllowListFilter(apfFilter: ApfFilter) { + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + + // Using scapy to generate IPv4 mDNS packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") + // ip = IP(src="192.168.1.1") + // udp = UDP(sport=5353, dport=5353) + // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) + // p = eth/ip/udp/dns + val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + + "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(mdnsPkt), + PASSED_IPV4 + ) + + // Using scapy to generate RA packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="33:33:00:00:00:01") + // ip6 = IPv6(src="fe80::1", dst="ff02::1") + // icmp6 = ICMPv6ND_RA(routerlifetime=3600, retranstimer=3600) + // p = eth/ip6/icmp6 + val raPkt = "333300000001e89f806660bb86dd6000000000103afffe800000000000000000000000" + + "000001ff0200000000000000000000000000018600600700080e100000000000000e10" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(raPkt), + PASSED_IPV6_ICMP + ) + + // Using scapy to generate ethernet packet with type 0x88A2: + // p = Ether(type=0x88A2)/Raw(load="01") + val ethPkt = "ffffffffffff047bcb463fb588a23031" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(ethPkt), + DROPPED_ETHERTYPE_NOT_ALLOWED + ) + } + + private fun generateNsPacket( + srcMac: ByteArray, + dstMac: ByteArray, + srcIp: ByteArray, + dstIp: ByteArray, + target: ByteArray, + ): ByteArray { + val nsPacketBuf = NeighborSolicitation.build( + MacAddress.fromBytes(srcMac), + MacAddress.fromBytes(dstMac), + InetAddress.getByAddress(srcIp) as Inet6Address, + InetAddress.getByAddress(dstIp) as Inet6Address, + InetAddress.getByAddress(target) as Inet6Address + ) + + val nsPacket = ByteArray( + ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NS_HEADER_LEN + 8 // option length + ) + nsPacketBuf.get(nsPacket) + return nsPacket + } + + private fun generateNaPacket( + srcMac: ByteArray, + dstMac: ByteArray, + srcIp: ByteArray, + dstIp: ByteArray, + flags: Int, + target: ByteArray, + ): ByteArray { + val naPacketBuf = NeighborAdvertisement.build( + MacAddress.fromBytes(srcMac), + MacAddress.fromBytes(dstMac), + InetAddress.getByAddress(srcIp) as Inet6Address, + InetAddress.getByAddress(dstIp) as Inet6Address, + flags, + InetAddress.getByAddress(target) as Inet6Address + ) + val naPacket = ByteArray( + ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NA_HEADER_LEN + 8 // lla option length + ) + + naPacketBuf.get(naPacket) + return naPacket + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testV4EtherTypeAllowListFilter() { + val apfFilter = getApfFilter(getDefaultConfig(APF_VERSION_3)) + doTestEtherTypeAllowListFilter(apfFilter) + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testV6EtherTypeAllowListFilter() { + val apfFilter = getApfFilter(getDefaultConfig(APF_VERSION_6)) + doTestEtherTypeAllowListFilter(apfFilter) + } + + @Test + fun testIPv4PacketFilterOnV6OnlyNetwork() { + val apfFilter = getApfFilter() + apfFilter.updateClatInterfaceState(true) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + + // Using scapy to generate IPv4 mDNS packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") + // ip = IP(src="192.168.1.1") + // udp = UDP(sport=5353, dport=5353) + // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) + // p = eth/ip/udp/dns + val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + + "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(mdnsPkt), + DROPPED_IPV4_NON_DHCP4 + ) + + // Using scapy to generate DHCP4 offer packet: + // ether = Ether(src='00:11:22:33:44:55', dst='ff:ff:ff:ff:ff:ff') + // ip = IP(src='192.168.1.1', dst='255.255.255.255') + // udp = UDP(sport=67, dport=68) + // bootp = BOOTP(op=2, + // yiaddr='192.168.1.100', + // siaddr='192.168.1.1', + // chaddr=b'\x00\x11\x22\x33\x44\x55') + // dhcp_options = [('message-type', 'offer'), + // ('server_id', '192.168.1.1'), + // ('subnet_mask', '255.255.255.0'), + // ('router', '192.168.1.1'), + // ('lease_time', 86400), + // ('name_server', '8.8.8.8'), + // 'end'] + // dhcp = DHCP(options=dhcp_options) + // dhcp_offer_packet = ether/ip/udp/bootp/dhcp + val dhcp4Pkt = + "ffffffffffff00112233445508004500012e000100004011b815c0a80101ffffffff0043" + + "0044011a5ffc02010600000000000000000000000000c0a80164c0a80101000000000011" + + "223344550000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000638253633501023604c0" + + "a801010104ffffff000304c0a80101330400015180060408080808ff" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(dhcp4Pkt), + PASSED_IPV4_FROM_DHCPV4_SERVER + ) + + // Using scapy to generate DHCP4 offer packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") + // ip = IP(src="192.168.1.10", dst="192.168.1.20") # IPv4 + // udp = UDP(sport=12345, dport=53) + // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) + // pkt = eth / ip / udp / dns + // fragments = fragment(pkt, fragsize=30) + // fragments[1] + val fragmentedUdpPkt = + "01005e0000fbe89f806660bb08004500001d000100034011f75dc0a8010ac0a8" + + "01146f63616c00000c0001" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(fragmentedUdpPkt), + DROPPED_IPV4_NON_DHCP4 + ) + } + + // The APFv6 code path is only turned on in V+ + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Test + fun testArpTransmit() { + val apfFilter = getApfFilter() + verify(ipClientCallback, times(2)).installPacketFilter(any()) + val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) + val lp = LinkProperties() + lp.addLinkAddress(linkAddress) + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.value + val receivedArpPacketBuf = ArpPacket.buildArpPacket( + arpBroadcastMacAddress, + senderMacAddress, + hostIpv4Address, + HexDump.hexStringToByteArray("000000000000"), + senderIpv4Address, + ARP_REQUEST.toShort() + ) + val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) + receivedArpPacketBuf.get(receivedArpPacket) + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedArpPacket, + DROPPED_ARP_REQUEST_REPLIED + ) + + val transmittedPacket = ApfJniUtils.getTransmittedPacket() + val expectedArpReplyBuf = ArpPacket.buildArpPacket( + senderMacAddress, + apfFilter.mHardwareAddress, + senderIpv4Address, + senderMacAddress, + hostIpv4Address, + ARP_REPLY.toShort() + ) + val expectedArpReplyPacket = ByteArray(ARP_ETHER_IPV4_LEN) + expectedArpReplyBuf.get(expectedArpReplyPacket) + assertContentEquals( + expectedArpReplyPacket + ByteArray(18) { 0 }, + transmittedPacket + ) + } + + @Test + fun testArpOffloadDisabled() { + val apfConfig = getDefaultConfig() + apfConfig.shouldHandleArpOffload = false + val apfFilter = getApfFilter(apfConfig) + verify(ipClientCallback, times(2)).installPacketFilter(any()) + val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) + val lp = LinkProperties() + lp.addLinkAddress(linkAddress) + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.value + val receivedArpPacketBuf = ArpPacket.buildArpPacket( + arpBroadcastMacAddress, + senderMacAddress, + hostIpv4Address, + HexDump.hexStringToByteArray("000000000000"), + senderIpv4Address, + ARP_REQUEST.toShort() + ) + val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) + receivedArpPacketBuf.get(receivedArpPacket) + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedArpPacket, + PASSED_ARP_REQUEST + ) + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testNsFilterNoIPv6() { + `when`(dependencies.getAnycast6Addresses(any())).thenReturn(listOf()) + val apfFilter = getApfFilter() + // validate NS packet check when there is no IPv6 address + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // pkt = eth/ip6/icmp6 + val nsPkt = "01020304050600010203040586DD6000000000183AFF200100000000000" + + "00200001A1122334420010000000000000200001A334411228700452900" + + "00000020010000000000000200001A33441122" + // when there is no IPv6 addresses -> pass NS packet + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nsPkt), + PASSED_IPV6_NS_NO_ADDRESS + ) + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testNsFilter() { + val apfFilter = getApfFilter() + verify(ipClientCallback, times(2)).installPacketFilter(any()) + + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + for (addr in hostIpv6TentativeAddresses) { + lp.addLinkAddress( + LinkAddress( + InetAddress.getByAddress(addr), + 64, + IFA_F_TENTATIVE, + 0 + ) + ) + } + + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(3)).installPacketFilter(any()) + apfFilter.updateClatInterfaceState(true) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(4)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.value + + // validate Ethernet dst address check + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="00:05:04:03:02:01") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonHostDstMacNsPkt = + "00050403020100010203040586DD6000000000203AFF2001000000000000" + + "0200001A1122334420010000000000000200001A3344112287003D170000" + + "000020010000000000000200001A334411220201000102030405" + // invalid unicast ether dst -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonHostDstMacNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:03:02:01") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonMcastDstMacNsPkt = "3333FF03020100010203040586DD6000000000203AFF20010000000000" + + "000200001A1122334420010000000000000200001A3344112287003D17" + + "0000000020010000000000000200001A334411220201000102030405" + // mcast dst mac is not one of solicited mcast mac derived from one of device's ip -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonMcastDstMacNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:44:11:22") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val hostMcastDstMacNsPkt = + "3333FF44112200010203040586DD6000000000203AFF20010000000000" + + "000200001A1122334420010000000000000200001A3344112287003E17" + + "0000000020010000000000000200001A334411220101000102030405" + // mcast dst mac is one of solicited mcast mac derived from one of device's ip + // -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(hostMcastDstMacNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val broadcastNsPkt = + "FFFFFFFFFFFF00010203040586DD6000000000203AFF200100000000000002000" + + "01A1122334420010000000000000200001A3344112287003E1700000000200100" + + "00000000000200001A334411220101000102030405" + // mcast dst mac is broadcast address -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(broadcastNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // validate IPv6 dst address check + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val validHostDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000" + + "00200001A1122334420010000000000000200001A3344112287003E1700" + + "00000020010000000000000200001A334411220101000102030405" + // dst ip is one of device's ip -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(validHostDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::100:1b:aabb:ccdd", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::100:1b:aabb:ccdd") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val validHostAnycastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF20010000" + + "000000000200001A1122334420010000000000000100001BAABB" + + "CCDD8700D9AE0000000020010000000000000100001BAABBCCDD" + + "0101000102030405" + // dst ip is device's anycast address -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(validHostAnycastDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:4444:5555", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonHostUcastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF2001000000000" + + "0000200001A1122334420010000000000000200001A444455558700E8" + + "E30000000020010000000000000200001A334411220101000102030405" + // unicast dst ip is not one of device's ip -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonHostUcastDstIpNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1133", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonHostMcastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF2001000000000" + + "0000200001A11223344FF0200000000000000000001FF441133870095" + + "1C0000000020010000000000000200001A334411220101000102030405" + // mcast dst ip is not one of solicited mcast ip derived from one of device's ip -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonHostMcastDstIpNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val hostMcastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF2001000000000000" + + "0200001A11223344FF0200000000000000000001FF4411228700952D0000" + + "000020010000000000000200001A334411220101000102030405" + // mcast dst ip is one of solicited mcast ip derived from one of device's ip + // -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // validate IPv6 NS payload check + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255, plen=20) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val shortNsPkt = + "02030405060700010203040586DD6000000000143AFF20010000000000000200001A1" + + "122334420010000000000000200001A3344112287003B140000000020010000000000" + + "000200001A334411220101010203040506" + // payload len < 24 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(shortNsPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:4444:5555") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val otherHostNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000002000" + + "01A1122334420010000000000000200001A334411228700E5E000000000200100" + + "00000000000200001A444455550101010203040506" + // target ip is not one of device's ip -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(otherHostNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=20) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val invalidHoplimitNsPkt = + "02030405060700010203040586DD6000000000203A14200100000000000" + + "00200001A1122334420010000000000000200001A3344112287003B1400" + + "00000020010000000000000200001A334411220101010203040506" + // hoplimit is not 255 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(invalidHoplimitNsPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122", code=5) + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val invalidIcmpCodeNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000" + + "00200001A1122334420010000000000000200001A3344112287053B0F00" + + "00000020010000000000000200001A334411220101010203040506" + // icmp6 code is not 0 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(invalidIcmpCodeNsPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:1234:5678") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val tentativeTargetIpNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000" + + "00000200001A1122334420010000000000000200001A334411228700" + + "16CE0000000020010000000000000200001A123456780101010203040506" + // target ip is one of tentative address -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(tentativeTargetIpNsPkt), + PASSED_IPV6_NS_TENTATIVE + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1c:2255:6666") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val invalidTargetIpNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000" + + "00200001A1122334420010000000000000200001A334411228700F6BC00" + + "00000020010000000000000200001C225566660101010203040506" + // target ip is none of {non-tentative, anycast} -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(invalidTargetIpNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="::", dst="ff02::1:ff44:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="02:03:04:05:06:07") + // pkt = eth/ip6/icmp6/icmp6_opt + val dadNsPkt = + "02030405060700010203040586DD6000000000203AFF000000000000000000000000000" + + "00000FF0200000000000000000001FF4411228700F4A800000000200100000000000002" + + "00001A334411220201020304050607" + // DAD NS request -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(dadNsPkt), + PASSED_IPV6_NS_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // pkt = eth/ip6/icmp6 + val noOptionNsPkt = + "02030405060700010203040586DD6000000000183AFF2001000000000000020000" + + "1A1122334420010000000000000200001A33441122870045290000000020010000" + + "000000000200001A33441122" + // payload len < 32 -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(noOptionNsPkt), + PASSED_IPV6_NS_NO_SLLA_OPTION + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="ff01::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonDadMcastSrcIpPkt = + "02030405060700010203040586DD6000000000203AFFFF01000000000000" + + "0200001A1122334420010000000000000200001A3344112287005C130000" + + "000020010000000000000200001A334411220101010203040506" + // non-DAD src IPv6 is FF::/8 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonDadMcastSrcIpPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="0001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonDadLoopbackSrcIpPkt = + "02030405060700010203040586DD6000000000203AFF0001000000000" + + "0000200001A1122334420010000000000000200001A3344112287005B" + + "140000000020010000000000000200001A334411220101010203040506" + // non-DAD src IPv6 is 00::/8 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonDadLoopbackSrcIpPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt1 = ICMPv6NDOptDstLLAddr(lladdr="01:02:03:04:05:06") + // icmp6_opt2 = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt1/icmp6_opt2 + val sllaNotFirstOptionNsPkt = + "02030405060700010203040586DD6000000000283AFF200100000000" + + "00000200001A1122334420010000000000000200001A334411228700" + + "2FFF0000000020010000000000000200001A33441122020101020304" + + "05060101010203040506" + // non-DAD with multiple options, SLLA in 2nd option -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(sllaNotFirstOptionNsPkt), + PASSED_IPV6_NS_NO_SLLA_OPTION + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val noSllaOptionNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000002" + + "00001A1122334420010000000000000200001A3344112287003A1400000000" + + "20010000000000000200001A334411220201010203040506" + // non-DAD with one option but not SLLA -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(noSllaOptionNsPkt), + PASSED_IPV6_NS_NO_SLLA_OPTION + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val mcastMacSllaOptionNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000" + + "00000200001A1122334420010000000000000200001A334411228700" + + "3B140000000020010000000000000200001A33441122010101020304" + + "0506" + // non-DAD, SLLA is multicast MAC -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(mcastMacSllaOptionNsPkt), + DROPPED_IPV6_NS_INVALID + ) + } + + // The APFv6 code path is only turned on in V+ + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Test + fun testNaTransmit() { + val apfFilter = getApfFilter() + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses + for (addr in validIpv6Addresses) { + // unicast solicited NS request + val receivedUcastNsPacket = generateNsPacket( + senderMacAddress, + apfFilter.mHardwareAddress, + senderIpv6Address, + addr, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedUcastNsPacket, + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + val transmittedUcastPacket = ApfJniUtils.getTransmittedPacket() + val expectedUcastNaPacket = generateNaPacket( + apfFilter.mHardwareAddress, + senderMacAddress, + addr, + senderIpv6Address, + 0xe0000000.toInt(), // R=1, S=1, O=1 + addr + ) + + assertContentEquals( + expectedUcastNaPacket, + transmittedUcastPacket + ) + + val solicitedMcastAddr = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast( + InetAddress.getByAddress(addr) as Inet6Address + )!! + val mcastDa = NetworkStackUtils.ipv6MulticastToEthernetMulticast(solicitedMcastAddr) + .toByteArray() + + // multicast solicited NS request + var receivedMcastNsPacket = generateNsPacket( + senderMacAddress, + mcastDa, + senderIpv6Address, + solicitedMcastAddr.address, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedMcastNsPacket, + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + val transmittedMcastPacket = ApfJniUtils.getTransmittedPacket() + val expectedMcastNaPacket = generateNaPacket( + apfFilter.mHardwareAddress, + senderMacAddress, + addr, + senderIpv6Address, + 0xe0000000.toInt(), // R=1, S=1, O=1 + addr + ) + + assertContentEquals( + expectedMcastNaPacket, + transmittedMcastPacket + ) + } + } + + // The APFv6 code path is only turned on in V+ + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Test + fun testNaTransmitWithTclass() { + // mock nd traffic class from /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass to 20 + `when`(dependencies.getNdTrafficClass(any())).thenReturn(20) + val apfFilter = getApfFilter() + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255, tc=20) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val hostMcastDstIpNsPkt = + "02030405060700010203040586DD6140000000203AFF2001000000000000" + + "0200001A11223344FF0200000000000000000001FF4411228700952D0000" + + "000020010000000000000200001A334411220101000102030405" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + val transmitPkt = ApfJniUtils.getTransmittedPacket() + // Using scapy to generate IPv6 NA packet: + // eth = Ether(src="02:03:04:05:06:07", dst="00:01:02:03:04:05") + // ip6 = IPv6(src="2001::200:1a:3344:1122", dst="2001::200:1a:1122:3344", hlim=255, tc=20) + // icmp6 = ICMPv6ND_NA(tgt="2001::200:1a:3344:1122", R=1, S=1, O=1) + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="02:03:04:05:06:07") + // pkt = eth/ip6/icmp6/icmp6_opt + val expectedNaPacket = + "00010203040502030405060786DD6140000000203AFF2001000000000000020" + + "0001A3344112220010000000000000200001A1122334488005610E000000020" + + "010000000000000200001A334411220201020304050607" + assertContentEquals( + HexDump.hexStringToByteArray(expectedNaPacket), + transmitPkt + ) + } + + @Test + fun testNdOffloadDisabled() { + val apfConfig = getDefaultConfig() + apfConfig.shouldHandleNdOffload = false + val apfFilter = getApfFilter(apfConfig) + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses + for (addr in validIpv6Addresses) { + // unicast solicited NS request + val receivedUcastNsPacket = generateNsPacket( + senderMacAddress, + apfFilter.mHardwareAddress, + senderIpv6Address, + addr, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedUcastNsPacket, + PASSED_IPV6_ICMP + ) + + val solicitedMcastAddr = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast( + InetAddress.getByAddress(addr) as Inet6Address + )!! + val mcastDa = NetworkStackUtils.ipv6MulticastToEthernetMulticast(solicitedMcastAddr) + .toByteArray() + + // multicast solicited NS request + var receivedMcastNsPacket = generateNsPacket( + senderMacAddress, + mcastDa, + senderIpv6Address, + solicitedMcastAddr.address, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedMcastNsPacket, + PASSED_IPV6_ICMP + ) + } + } + + @Test + fun testApfProgramUpdate() { + val apfFilter = getApfFilter() + verify(ipClientCallback, times(2)).installPacketFilter(any()) + // add IPv4 address, expect to have apf program update + val lp = LinkProperties() + val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) + lp.addLinkAddress(linkAddress) + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(3)).installPacketFilter(any()) + + // add the same IPv4 address, expect to have no apf program update + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(3)).installPacketFilter(any()) + + // add IPv6 addresses, expect to have apf program update + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(4)).installPacketFilter(any()) + + // add the same IPv6 addresses, expect to have no apf program update + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(4)).installPacketFilter(any()) + + // add more tentative IPv6 addresses, expect to have apf program update + for (addr in hostIpv6TentativeAddresses) { + lp.addLinkAddress( + LinkAddress( + InetAddress.getByAddress(addr), + 64, + IFA_F_TENTATIVE, + 0 + ) + ) + } + + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(5)).installPacketFilter(any()) + + // add the same IPv6 addresses, expect to have no apf program update + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(5)).installPacketFilter(any()) + } + + @Test + fun testApfFilterInitializationCleanUpTheApfMemoryRegion() { + val apfFilter = getApfFilter() + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.first() + assertContentEquals(ByteArray(4096) { 0 }, program) + } +}
diff --git a/tests/unit/src/android/net/apf/ApfNewTest.kt b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt similarity index 62% rename from tests/unit/src/android/net/apf/ApfNewTest.kt rename to tests/unit/src/android/net/apf/ApfGeneratorTest.kt index 6863fb9..1364af5 100644 --- a/tests/unit/src/android/net/apf/ApfNewTest.kt +++ b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
@@ -15,31 +15,16 @@ */ package android.net.apf -import android.content.Context -import android.net.LinkAddress -import android.net.LinkProperties -import android.net.MacAddress import android.net.apf.ApfCounterTracker.Counter -import android.net.apf.ApfCounterTracker.Counter.APF_PROGRAM_ID -import android.net.apf.ApfCounterTracker.Counter.APF_VERSION import android.net.apf.ApfCounterTracker.Counter.CORRUPT_DNS_PACKET -import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST -import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4 -import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID -import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST import android.net.apf.ApfCounterTracker.Counter.PASSED_ALLOCATE_FAILURE import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP -import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4 -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_MULTIPLE_OPTIONS -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS import android.net.apf.ApfCounterTracker.Counter.PASSED_TRANSMIT_FAILURE import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS -import android.net.apf.ApfFilter.Dependencies +import android.net.apf.ApfTestHelpers.Companion.decodeCountersIntoMap +import android.net.apf.ApfTestHelpers.Companion.verifyProgramRun import android.net.apf.ApfTestUtils.DROP import android.net.apf.ApfTestUtils.MIN_PKT_SIZE import android.net.apf.ApfTestUtils.PASS @@ -55,142 +40,53 @@ import android.net.apf.BaseApfGenerator.PASS_LABEL import android.net.apf.BaseApfGenerator.Register.R0 import android.net.apf.BaseApfGenerator.Register.R1 -import android.net.ip.IpClient.IpClientCallbacksWrapper -import android.os.Build -import android.system.OsConstants.ARPHRD_ETHER -import android.system.OsConstants.IFA_F_TENTATIVE import androidx.test.filters.SmallTest import com.android.net.module.util.HexDump -import com.android.net.module.util.InterfaceParams -import com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN -import com.android.net.module.util.NetworkStackConstants.ARP_REPLY -import com.android.net.module.util.NetworkStackConstants.ARP_REQUEST import com.android.net.module.util.Struct -import com.android.net.module.util.arp.ArpPacket import com.android.net.module.util.structs.EthernetHeader import com.android.net.module.util.structs.Ipv4Header import com.android.net.module.util.structs.UdpHeader -import com.android.networkstack.metrics.NetworkQuirkMetrics import com.android.testutils.DevSdkIgnoreRule -import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRunner -import java.net.InetAddress import java.nio.ByteBuffer import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFailsWith -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any -import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations const val ETH_HLEN = 14 const val IPV4_HLEN = 20 const val IPPROTO_UDP = 17 /** - * Tests for APF instructions. + * Tests for APF generator instructions. */ @RunWith(DevSdkIgnoreRunner::class) @SmallTest -class ApfNewTest { +class ApfGeneratorTest { @get:Rule val ignoreRule = DevSdkIgnoreRule() - @Mock private lateinit var context: Context - - @Mock private lateinit var metrics: NetworkQuirkMetrics - - @Mock private lateinit var dependencies: Dependencies - - @Mock private lateinit var ipClientCallback: IpClientCallbacksWrapper - - private val defaultMaximumApfProgramSize = 2048 - - private val loInterfaceParams = InterfaceParams.getByName("lo") - - private val ifParams = - InterfaceParams( - "lo", - loInterfaceParams.index, - MacAddress.fromBytes(byteArrayOf(2, 3, 4, 5, 6, 7)), - loInterfaceParams.defaultMtu - ) + private val ramSize = 2048 + private val clampSize = 2048 private val testPacket = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) - private val hostIpv4Address = byteArrayOf(10, 0, 0, 1) - private val senderIpv4Address = byteArrayOf(10, 0, 0, 2) - private val arpBroadcastMacAddress = intArrayOf(0xff, 0xff, 0xff, 0xff, 0xff, 0xff) - .map { it.toByte() }.toByteArray() - private val senderMacAddress = intArrayOf(0x01, 0x22, 0x33, 0x44, 0x55, 0x66) - .map { it.toByte() }.toByteArray() - private val hostIpv6Addresses = listOf( - // 2001::200:1a:3344:1122 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x33, 0x44, 0x11, 0x22) - .map{ it.toByte() }.toByteArray(), - // 2001::100:1b:4455:6677 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x44, 0x55, 0x66, 0x77) - .map{ it.toByte() }.toByteArray() - ) - private val hostIpv6TentativeAddresses = listOf( - // 2001::200:1a:1234:5678 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x12, 0x34, 0x56, 0x78) - .map{ it.toByte() }.toByteArray(), - // 2001::100:1b:1234:5678 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x12, 0x34, 0x56, 0x78) - .map{ it.toByte() }.toByteArray() - ) - private val hostAnycast6Addresses = listOf( - // 2001::100:1b:aabb:ccdd - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0xaa, 0xbb, 0xcc, 0xdd) - .map{ it.toByte() }.toByteArray() - ) - private val hostMulticastMacAddresses = listOf( - // 33:33:00:00:00:01 - intArrayOf(0x33, 0x33, 0, 0, 0, 1).map { it.toByte() }.toByteArray(), - // 33:33:ff:44:11:22 - intArrayOf(0x33, 0x33, 0xff, 0x44, 0x11, 0x22).map { it.toByte() }.toByteArray(), - // 33:33:ff:55:66:77 - intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(), - // 33:33:ff:bb:cc:dd - intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(), - ) - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - // mock anycast6 address from /proc/net/anycast6 - `when`(dependencies.getAnycast6Addresses(any())).thenReturn(hostAnycast6Addresses) - // mock host mac address and ethernet multicast addresses from /proc/net/dev_mcast - `when`(dependencies.getEtherMulticastAddresses(any())).thenReturn(hostMulticastMacAddresses) - } - - @After - fun tearDown() { - Mockito.framework().clearInlineMocks() - ApfJniUtils.resetTransmittedPacketMemory() - } @Test fun testDataInstructionMustComeFirst() { - var gen = ApfV6Generator(defaultMaximumApfProgramSize) + var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addAllocateR0() assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } } @Test fun testApfInstructionEncodingSizeCheck() { - var gen = ApfV6Generator(defaultMaximumApfProgramSize) + var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) assertFailsWith<IllegalArgumentException> { - ApfV6Generator(ByteArray(65536) { 0x01 }, defaultMaximumApfProgramSize) + ApfV6Generator(ByteArray(65536) { 0x01 }, APF_VERSION_6, ramSize, clampSize) } assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) } assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) } @@ -439,7 +335,7 @@ ) } - val v4gen = ApfV4Generator(APF_VERSION_3) + val v4gen = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) assertFailsWith<IllegalArgumentException> { v4gen.addCountAndDrop(PASSED_ARP) } assertFailsWith<IllegalArgumentException> { v4gen.addCountAndPass(DROPPED_ETH_BROADCAST) } assertFailsWith<IllegalArgumentException> { @@ -517,7 +413,7 @@ fun testValidateDnsNames() { // '%' is a valid label character in mDNS subtype // byte == 0xff means it is a '*' wildcard, which is a valid encoding. - val program = ApfV6Generator(defaultMaximumApfProgramSize).addJumpIfPktAtR0ContainDnsQ( + val program = ApfV6Generator(ramSize, ramSize, clampSize).addJumpIfPktAtR0ContainDnsQ( byteArrayOf(1, '%'.code.toByte(), 0, 0), 1, DROP_LABEL @@ -529,7 +425,7 @@ @Test fun testApfInstructionsEncoding() { - val v4gen = ApfV4Generator(APF_VERSION_2) + val v4gen = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) v4gen.addPass() var program = v4gen.generate() // encoding PASS opcode: opcode=0, imm_len=0, R=0 @@ -542,7 +438,7 @@ ApfJniUtils.disassembleApf(program).map { it.trim() } ) - var gen = ApfV6Generator(defaultMaximumApfProgramSize) + var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addDrop() program = gen.generate().skipDataAndDebug() // encoding DROP opcode: opcode=0, imm_len=0, R=1 @@ -555,7 +451,7 @@ ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addCountAndPass(129) program = gen.generate().skipDataAndDebug() // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber @@ -571,7 +467,7 @@ ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addCountAndDrop(1000) program = gen.generate().skipDataAndDebug() // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber @@ -588,7 +484,7 @@ ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addCountAndPass(PASSED_ARP) program = gen.generate().skipDataAndDebug() // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber @@ -604,7 +500,7 @@ ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addCountAndDrop(DROPPED_ETHERTYPE_NOT_ALLOWED) program = gen.generate().skipDataAndDebug() // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber @@ -616,11 +512,11 @@ program ) assertContentEquals( - listOf("0: drop counter=43"), + listOf("0: drop counter=46"), ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addAllocateR0() gen.addAllocate(1500) program = gen.generate().skipDataAndDebug() @@ -642,7 +538,7 @@ "2: allocate 1500" ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addTransmitWithoutChecksum() gen.addTransmitL4(30, 40, 50, 256, true) program = gen.generate().skipDataAndDebug() @@ -659,25 +555,25 @@ ), ApfJniUtils.disassembleApf(program).map { it.trim() }) val largeByteArray = ByteArray(256) { 0x01 } - gen = ApfV6Generator(largeByteArray, defaultMaximumApfProgramSize) + gen = ApfV6Generator(largeByteArray, APF_VERSION_6, ramSize, clampSize) program = gen.generate() assertContentEquals( byteArrayOf( encodeInstruction(opcode = 14, immLength = 2, register = 1), 1, 0 ) + largeByteArray + byteArrayOf( - encodeInstruction(opcode = 21, immLength = 1, register = 0), 48, 6, 25 + encodeInstruction(opcode = 21, immLength = 1, register = 0), 48, 6, 13 ), program ) assertContentEquals( listOf( "0: data 256, " + "01".repeat(256), - "259: debugbuf size=1561" + "259: debugbuf size=1549" ), ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addWriteU8(0x01) gen.addWriteU16(0x0102) gen.addWriteU32(0x01020304) @@ -718,7 +614,7 @@ "35: write 0xfffefdfc" ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addWriteU8(R0) gen.addWriteU16(R0) gen.addWriteU32(R0) @@ -743,7 +639,7 @@ "10: ewrite4 r1" ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addDataCopy(0, 10) gen.addDataCopy(1, 5) gen.addPacketCopy(1000, 255) @@ -760,7 +656,7 @@ "5: pktcopy src=1000, len=255" ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addDataCopyFromR0(5) gen.addPacketCopyFromR0(5) gen.addDataCopyFromR0LenR1() @@ -779,7 +675,7 @@ "8: epktcopy src=r0, len=r1" ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfV4Generator.DROP_LABEL) program = gen.generate().skipDataAndDebug() assertContentEquals(byteArrayOf( @@ -793,7 +689,7 @@ ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) program = gen.generate().skipDataAndDebug() @@ -807,7 +703,7 @@ "10: jdnsqeq r0, DROP, 12, (1)A(1)B(0)(0)" ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfPktAtR0DoesNotContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL) gen.addJumpIfPktAtR0ContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL) program = gen.generate().skipDataAndDebug() @@ -821,7 +717,7 @@ "10: jdnsqeqsafe r0, DROP, 12, (1)A(1)B(0)(0)" ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfV4Generator.DROP_LABEL) gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfV4Generator.DROP_LABEL) program = gen.generate().skipDataAndDebug() @@ -835,7 +731,7 @@ "9: jdnsaeq r0, DROP, (1)A(1)B(0)(0)" ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfPktAtR0DoesNotContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL) gen.addJumpIfPktAtR0ContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL) program = gen.generate().skipDataAndDebug() @@ -849,7 +745,7 @@ "9: jdnsaeqsafe r0, DROP, (1)A(1)B(0)(0)" ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfOneOf(R1, List(32) { (it + 1).toLong() }.toSet(), DROP_LABEL) gen.addJumpIfOneOf(R0, setOf(0, 257, 65536), DROP_LABEL) gen.addJumpIfNoneOf(R0, setOf(1, 2, 3), DROP_LABEL) @@ -863,7 +759,7 @@ encodeInstruction(21, 1, 0), 47, 1, 9, 1, 2, 3 ), program) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfOneOf(R0, setOf(0, 128, 256, 65536), DROP_LABEL) gen.addJumpIfNoneOf(R1, setOf(0, 128, 256, 65536), DROP_LABEL) program = gen.generate().skipDataAndDebug() @@ -872,7 +768,7 @@ "20: jnoneof r1, DROP, { 0, 128, 256, 65536 }" ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - gen = ApfV6Generator(defaultMaximumApfProgramSize) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) gen.addJumpIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL) gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL) gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 1), byteArrayOf(1, 1)), DROP_LABEL) @@ -894,7 +790,7 @@ @Test fun testWriteToTxBuffer() { - var program = ApfV6Generator(defaultMaximumApfProgramSize) + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addAllocate(14) .addWriteU8(0x01) .addWriteU16(0x0203) @@ -921,7 +817,7 @@ @Test fun testCopyToTxBuffer() { - var program = ApfV6Generator(byteArrayOf(33, 34, 35), defaultMaximumApfProgramSize) + var program = ApfV6Generator(byteArrayOf(33, 34, 35), APF_VERSION_6, ramSize, clampSize) .addAllocate(14) .addDataCopy(3, 2) // arg1=src, arg2=len .addDataCopy(5, 1) // arg1=src, arg2=len @@ -948,7 +844,7 @@ @Test fun testCopyContentToTxBuffer() { - val program = ApfV6Generator(defaultMaximumApfProgramSize) + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addAllocate(18) .addDataCopy(HexDump.hexStringToByteArray("112233445566")) .addDataCopy(HexDump.hexStringToByteArray("223344")) @@ -958,7 +854,7 @@ .generate() assertContentEquals(listOf( "0: data 9, 112233445566778899", - "12: debugbuf size=1788", + "12: debugbuf size=1776", "16: allocate 18", "20: datacopy src=3, len=6", "23: datacopy src=4, len=3", @@ -973,18 +869,18 @@ @Test fun testPassDrop() { - var program = ApfV6Generator(defaultMaximumApfProgramSize) + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addDrop() .addPass() .generate() assertDrop(APF_VERSION_6, program, testPacket) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) .generate() verifyProgramRun(APF_VERSION_6, program, testPacket, DROPPED_ETH_BROADCAST) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addCountAndPass(Counter.PASSED_ARP) .generate() verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP) @@ -994,11 +890,11 @@ fun testLoadStoreCounter() { doTestLoadStoreCounter ( { mutableMapOf() }, - { ApfV4Generator(APF_VERSION_3) } + { ApfV4Generator(APF_VERSION_3, ramSize, clampSize) } ) doTestLoadStoreCounter ( { mutableMapOf(TOTAL_PACKETS to 1) }, - { ApfV6Generator(defaultMaximumApfProgramSize) } + { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) } ) } @@ -1021,7 +917,7 @@ @Test fun testV4CountAndPassDropCompareR0() { doTestCountAndPassDropCompareR0( - getGenerator = { ApfV4Generator(APF_VERSION_3) }, + getGenerator = { ApfV4Generator(APF_VERSION_3, ramSize, clampSize) }, incTotal = false ) } @@ -1029,7 +925,7 @@ @Test fun testV6CountAndPassDropCompareR0() { doTestCountAndPassDropCompareR0( - getGenerator = { ApfV6Generator(defaultMaximumApfProgramSize) }, + getGenerator = { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) }, incTotal = true ) } @@ -1341,72 +1237,9 @@ verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) } - private fun doTestEtherTypeAllowListFilter(apfVersion: Int) { - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - val apfFilter = - ApfFilter( - context, - getDefaultConfig(apfVersion), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - - // Using scapy to generate IPv4 mDNS packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") - // ip = IP(src="192.168.1.1") - // udp = UDP(sport=5353, dport=5353) - // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) - // p = eth/ip/udp/dns - val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + - "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" - verifyProgramRun(APF_VERSION_6, program, HexDump.hexStringToByteArray(mdnsPkt), PASSED_IPV4) - - // Using scapy to generate RA packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="33:33:00:00:00:01") - // ip6 = IPv6(src="fe80::1", dst="ff02::1") - // icmp6 = ICMPv6ND_RA(routerlifetime=3600, retranstimer=3600) - // p = eth/ip6/icmp6 - val raPkt = "333300000001e89f806660bb86dd6000000000103afffe800000000000000000000000" + - "000001ff0200000000000000000000000000018600600700080e100000000000000e10" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(raPkt), - PASSED_IPV6_ICMP - ) - - // Using scapy to generate ethernet packet with type 0x88A2: - // p = Ether(type=0x88A2)/Raw(load="01") - val ethPkt = "ffffffffffff047bcb463fb588a23031" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(ethPkt), - DROPPED_ETHERTYPE_NOT_ALLOWED - ) - - apfFilter.shutdown() - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testV4EtherTypeAllowListFilter() { - doTestEtherTypeAllowListFilter(APF_VERSION_3) - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testV6EtherTypeAllowListFilter() { - doTestEtherTypeAllowListFilter(APF_VERSION_6) - } - @Test fun testV4CountAndPassDrop() { - var program = ApfV4Generator(APF_VERSION_3) + var program = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) .addCountTrampoline() .generate() @@ -1418,7 +1251,7 @@ incTotal = false ) - program = ApfV4Generator(APF_VERSION_3) + program = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) .addCountAndPass(Counter.PASSED_ARP) .addCountTrampoline() .generate() @@ -1427,7 +1260,7 @@ @Test fun testV2CountAndPassDrop() { - var program = ApfV4Generator(APF_VERSION_2) + var program = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) .addCountTrampoline() .generate() @@ -1435,7 +1268,7 @@ assertVerdict(APF_VERSION_6, DROP, program, testPacket, dataRegion) assertContentEquals(ByteArray(Counter.totalSize()) { 0 }, dataRegion) - program = ApfV4Generator(APF_VERSION_2) + program = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) .addCountAndPass(PASSED_ARP) .addCountTrampoline() .generate() @@ -1446,7 +1279,7 @@ @Test fun testAllocateFailure() { - val program = ApfV6Generator(defaultMaximumApfProgramSize) + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) // allocate size: 65535 > sizeof(apf_test_buffer): 1514, trigger allocate failure. .addAllocate(65535) .addDrop() @@ -1456,7 +1289,7 @@ @Test fun testTransmitFailure() { - val program = ApfV6Generator(defaultMaximumApfProgramSize) + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addAllocate(14) // len: 13 is less than ETH_HLEN, trigger transmit failure. .addLoadImmediate(R0, 13) @@ -1492,7 +1325,7 @@ 0x00, 0x00, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, ).map { it.toByte() }.toByteArray() - val program = ApfV6Generator(etherIpv4UdpPacket, defaultMaximumApfProgramSize) + val program = ApfV6Generator(etherIpv4UdpPacket, APF_VERSION_6, ramSize, clampSize) .addAllocate(etherIpv4UdpPacket.size) .addDataCopy(3, etherIpv4UdpPacket.size) // arg1=src, arg2=len .addTransmitL4( @@ -1538,28 +1371,28 @@ 0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001 ).map { it.toByte() }.toByteArray() - var program = ApfV6Generator(defaultMaximumApfProgramSize) + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype .addPass() .generate() assertDrop(APF_VERSION_6, program, udpPayload) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) .addPass() .generate() assertDrop(APF_VERSION_6, program, udpPayload) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0DoesNotContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype .addPass() .generate() assertPass(APF_VERSION_6, program, udpPayload) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0DoesNotContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype .addPass() @@ -1581,14 +1414,14 @@ 0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001 ).map { it.toByte() }.toByteArray() - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype .addPass() .generate() verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype .addPass() @@ -1627,28 +1460,28 @@ 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9 ).map { it.toByte() }.toByteArray() - var program = ApfV6Generator(defaultMaximumApfProgramSize) + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL) .addPass() .generate() assertDrop(APF_VERSION_6, program, udpPayload) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL) .addPass() .generate() assertDrop(APF_VERSION_6, program, udpPayload) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0DoesNotContainDnsA(needlesMatch, DROP_LABEL) .addPass() .generate() assertPass(APF_VERSION_6, program, udpPayload) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0DoesNotContainDnsASafe(needlesMatch, DROP_LABEL) .addPass() @@ -1674,14 +1507,14 @@ 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9 ).map { it.toByte() }.toByteArray() - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL) .addPass() .generate() verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL) .addPass() @@ -1698,7 +1531,7 @@ @Test fun testJumpMultipleByteSequencesMatch() { - var program = ApfV6Generator(defaultMaximumApfProgramSize) + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfBytesAtR0EqualsAnyOf( listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), @@ -1708,7 +1541,7 @@ .generate() assertDrop(APF_VERSION_6, program, testPacket) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 2) .addJumpIfBytesAtR0EqualsAnyOf( listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), @@ -1718,7 +1551,7 @@ .generate() assertPass(APF_VERSION_6, program, testPacket) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 1) .addJumpIfBytesAtR0EqualNoneOf( listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), @@ -1728,7 +1561,7 @@ .generate() assertDrop(APF_VERSION_6, program, testPacket) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 0) .addJumpIfBytesAtR0EqualNoneOf( listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), @@ -1741,28 +1574,28 @@ @Test fun testJumpOneOf() { - var program = ApfV6Generator(defaultMaximumApfProgramSize) + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 255) .addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) .addPass() .generate() assertDrop(APF_VERSION_6, program, testPacket) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 254) .addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) .addPass() .generate() assertPass(APF_VERSION_6, program, testPacket) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 254) .addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) .addPass() .generate() assertDrop(APF_VERSION_6, program, testPacket) - program = ApfV6Generator(defaultMaximumApfProgramSize) + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoadImmediate(R0, 255) .addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) .addPass() @@ -1772,575 +1605,16 @@ @Test fun testDebugBuffer() { - val program = ApfV6Generator(defaultMaximumApfProgramSize) + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) .addLoad8(R0, 255) .generate() - val dataRegion = ByteArray(defaultMaximumApfProgramSize - program.size) { 0 } + val dataRegion = ByteArray(ramSize - program.size) { 0 } assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion) // offset 3 in the data region should contain if the interpreter is APFv6 mode or not assertEquals(1, dataRegion[3]) } - @Test - fun testIPv4PacketFilterOnV6OnlyNetwork() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - apfFilter.updateClatInterfaceState(true) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - - // Using scapy to generate IPv4 mDNS packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") - // ip = IP(src="192.168.1.1") - // udp = UDP(sport=5353, dport=5353) - // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) - // p = eth/ip/udp/dns - val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + - "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(mdnsPkt), - DROPPED_IPV4_NON_DHCP4 - ) - - // Using scapy to generate DHCP4 offer packet: - // ether = Ether(src='00:11:22:33:44:55', dst='ff:ff:ff:ff:ff:ff') - // ip = IP(src='192.168.1.1', dst='255.255.255.255') - // udp = UDP(sport=67, dport=68) - // bootp = BOOTP(op=2, - // yiaddr='192.168.1.100', - // siaddr='192.168.1.1', - // chaddr=b'\x00\x11\x22\x33\x44\x55') - // dhcp_options = [('message-type', 'offer'), - // ('server_id', '192.168.1.1'), - // ('subnet_mask', '255.255.255.0'), - // ('router', '192.168.1.1'), - // ('lease_time', 86400), - // ('name_server', '8.8.8.8'), - // 'end'] - // dhcp = DHCP(options=dhcp_options) - // dhcp_offer_packet = ether/ip/udp/bootp/dhcp - val dhcp4Pkt = "ffffffffffff00112233445508004500012e000100004011b815c0a80101ffffffff0043" + - "0044011a5ffc02010600000000000000000000000000c0a80164c0a80101000000000011" + - "223344550000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "0000000000000000000000000000000000000000000000000000638253633501023604c0" + - "a801010104ffffff000304c0a80101330400015180060408080808ff" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(dhcp4Pkt), - PASSED_IPV4_FROM_DHCPV4_SERVER - ) - - // Using scapy to generate DHCP4 offer packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") - // ip = IP(src="192.168.1.10", dst="192.168.1.20") # IPv4 - // udp = UDP(sport=12345, dport=53) - // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) - // pkt = eth / ip / udp / dns - // fragments = fragment(pkt, fragsize=30) - // fragments[1] - val fragmentedUdpPkt = "01005e0000fbe89f806660bb08004500001d000100034011f75dc0a8010ac0a8" + - "01146f63616c00000c0001" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(fragmentedUdpPkt), - DROPPED_IPV4_NON_DHCP4 - ) - apfFilter.shutdown() - } - - // The APFv6 code path is only turned on in V+ - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - @Test - fun testArpTransmit() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(any()) - val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) - val lp = LinkProperties() - lp.addLinkAddress(linkAddress) - apfFilter.setLinkProperties(lp) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.value - val receivedArpPacketBuf = ArpPacket.buildArpPacket( - arpBroadcastMacAddress, - senderMacAddress, - hostIpv4Address, - HexDump.hexStringToByteArray("000000000000"), - senderIpv4Address, - ARP_REQUEST.toShort() - ) - val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) - receivedArpPacketBuf.get(receivedArpPacket) - verifyProgramRun(APF_VERSION_6, program, receivedArpPacket, DROPPED_ARP_REQUEST_REPLIED) - - val transmittedPacket = ApfJniUtils.getTransmittedPacket() - val expectedArpReplyBuf = ArpPacket.buildArpPacket( - senderMacAddress, - apfFilter.mHardwareAddress, - senderIpv4Address, - senderMacAddress, - hostIpv4Address, - ARP_REPLY.toShort() - ) - val expectedArpReplyPacket = ByteArray(ARP_ETHER_IPV4_LEN) - expectedArpReplyBuf.get(expectedArpReplyPacket) - assertContentEquals( - expectedArpReplyPacket + ByteArray(18) {0}, - transmittedPacket - ) - apfFilter.shutdown() - } - - @Test - fun testArpOffloadDisabled() { - val apfConfig = getDefaultConfig() - apfConfig.shouldHandleArpOffload = false - val apfFilter = - ApfFilter( - context, - apfConfig, - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(any()) - val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) - val lp = LinkProperties() - lp.addLinkAddress(linkAddress) - apfFilter.setLinkProperties(lp) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.value - val receivedArpPacketBuf = ArpPacket.buildArpPacket( - arpBroadcastMacAddress, - senderMacAddress, - hostIpv4Address, - HexDump.hexStringToByteArray("000000000000"), - senderIpv4Address, - ARP_REQUEST.toShort() - ) - val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) - receivedArpPacketBuf.get(receivedArpPacket) - verifyProgramRun(APF_VERSION_6, program, receivedArpPacket, PASSED_ARP_REQUEST) - apfFilter.shutdown() - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testNsFilterNoIPv6() { - `when`(dependencies.getAnycast6Addresses(any())).thenReturn(listOf()) - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - - // validate NS packet check when there is no IPv6 address - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val nsPkt = "01020304050600010203040586DD6000000000183AFF200100000000000" + - "00200001A1122334420010000000000000200001A334411228700452900" + - "00000020010000000000000200001A33441122" - // when there is no IPv6 addresses -> pass NS packet - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nsPkt), - PASSED_IPV6_NS_NO_ADDRESS - ) - - apfFilter.shutdown() - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testNsFilter() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(any()) - - // validate Ethernet dst address check - - val lp = LinkProperties() - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(3)).installPacketFilter(any()) - apfFilter.updateClatInterfaceState(true) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(4)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.value - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="00:05:04:03:02:01") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val nonHostDstMacNsPkt = "00050403020100010203040586DD6000000000183AFF2001000000000000" + - "0200001A1122334420010000000000000200001A33441122870045290000" + - "000020010000000000000200001A33441122" - // invalid unicast ether dst -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonHostDstMacNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:03:02:01") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val nonMcastDstMacNsPkt = "3333ff03020100010203040586DD6000000000183AFF2001000000000000" + - "0200001A1122334420010000000000000200001A33441122870045290000" + - "000020010000000000000200001A33441122" - // mcast dst mac is not one of solicited mcast mac derived from one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonMcastDstMacNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:44:11:22") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val hostMcastDstMacNsPkt = "3333ff44112200010203040586DD6000000000183AFF2001000000000000" + - "0200001A1122334420010000000000000200001A33441122870045290000" + - "000020010000000000000200001A33441122" - // mcast dst mac is one of solicited mcast mac derived from one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(hostMcastDstMacNsPkt), - PASSED_IPV6_ICMP - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val broadcastNsPkt = "FFFFFFFFFFFF00010203040586DD6000000000183AFF2001000000000000" + - "0200001A1122334420010000000000000200001A33441122870045290000" + - "000020010000000000000200001A33441122" - // mcast dst mac is broadcast address -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(broadcastNsPkt), - PASSED_IPV6_ICMP - ) - - // validate IPv6 dst address check - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val validHostDstIpNsPkt = "02030405060700010203040586DD6000000000183AFF200100000000000" + - "00200001A1122334420010000000000000200001A334411228700452900" + - "00000020010000000000000200001A33441122" - // dst ip is one of device's ip -> Pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(validHostDstIpNsPkt), - PASSED_IPV6_ICMP - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::100:1b:aabb:ccdd", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::100:1b:aabb:ccdd") - // pkt = eth/ip6/icmp6 - val validHostAnycastDstIpNsPkt = "02030405060700010203040586DD6000000000183AFF20010000" + - "000000000200001A1122334420010000000000000100001BAABB" + - "CCDD8700E0C00000000020010000000000000100001BAABBCCDD" - // dst ip is device's anycast address -> Pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(validHostAnycastDstIpNsPkt), - PASSED_IPV6_ICMP - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:4444:5555", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val nonHostUcastDstIpNsPkt = "02030405060700010203040586DD6000000000183AFF200100000000" + - "00000200001A1122334420010000000000000200001A444455558700" + - "EFF50000000020010000000000000200001A33441122" - // unicast dst ip is not one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonHostUcastDstIpNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1133", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val nonHostMcastDstIpNsPkt = "02030405060700010203040586DD6000000000183AFF200100000000" + - "00000200001A11223344FF0200000000000000000001FF4411338700" + - "9C2E0000000020010000000000000200001A33441122" - // mcast dst ip is not one of solicited mcast ip derived from one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonHostMcastDstIpNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val hostMcastDstIpNsPkt = "02030405060700010203040586DD6000000000183AFF200100000000" + - "00000200001A11223344FF0200000000000000000001FF4411228700" + - "9C2E0000000020010000000000000200001A33441122" - // mcast dst ip is one of solicited mcast ip derived from one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), - PASSED_IPV6_ICMP - ) - - // validate IPv6 NS payload check - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255, plen=20) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val shortNsPkt = "02030405060700010203040586DD6000000000143AFF20010000000000000200001A1" + - "122334420010000000000000200001A3344112287003B140000000020010000000000" + - "000200001A334411220101010203040506" - // payload len < 24 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(shortNsPkt), - DROPPED_IPV6_NS_INVALID - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt_1 = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // icmp6_opt_2 = ICMPv6NDOptUnknown(type=14, len=6, data='\x11\x22\x33\x44\x55\x66') - // pkt = eth/ip6/icmp6/icmp6_opt_1/icmp6_opt_2 - val longNsPkt = "02030405060700010203040586DD6000000000283AFF20010000000000000200001A11" + - "22334420010000000000000200001A3344112287009339000000002001000000000000" + - "0200001A3344112201010102030405060E06112233445566" - // payload len > 32 -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(longNsPkt), - PASSED_IPV6_NS_MULTIPLE_OPTIONS - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:4444:5555") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val otherHostNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000000002000" + - "01A1122334420010000000000000200001A334411228700E5E000000000200100" + - "00000000000200001A444455550101010203040506" - // target ip is not one of device's ip -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(otherHostNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=20) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val invalidHoplimitNsPkt = "02030405060700010203040586DD6000000000203A14200100000000000" + - "00200001A1122334420010000000000000200001A3344112287003B1400" + - "00000020010000000000000200001A334411220101010203040506" - // hoplimit is not 255 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(invalidHoplimitNsPkt), - DROPPED_IPV6_NS_INVALID - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122", code=5) - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val invalidIcmpCodeNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000000" + - "00200001A1122334420010000000000000200001A3344112287053B0F00" + - "00000020010000000000000200001A334411220101010203040506" - // icmp6 code is not 0 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(invalidIcmpCodeNsPkt), - DROPPED_IPV6_NS_INVALID - ) - - apfFilter.shutdown() - } - - @Test - fun testApfProgramUpdate() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - - verify(ipClientCallback, times(2)).installPacketFilter(any()) - // add IPv4 address, expect to have apf program update - val lp = LinkProperties() - val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) - lp.addLinkAddress(linkAddress) - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(3)).installPacketFilter(any()) - - // add the same IPv4 address, expect to have no apf program update - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(3)).installPacketFilter(any()) - - // add IPv6 addresses, expect to have apf program update - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(4)).installPacketFilter(any()) - - // add the same IPv6 addresses, expect to have no apf program update - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(4)).installPacketFilter(any()) - - // add more tentative IPv6 addresses, expect to have apf program update - for (addr in hostIpv6TentativeAddresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64, IFA_F_TENTATIVE, 0)) - } - - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(5)).installPacketFilter(any()) - - // add the same IPv6 addresses, expect to have no apf program update - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(5)).installPacketFilter(any()) - apfFilter.shutdown() - } - - private fun verifyProgramRun( - version: Int, - program: ByteArray, - pkt: ByteArray, - targetCnt: Counter, - cntMap: MutableMap<Counter, Long> = mutableMapOf(), - dataRegion: ByteArray = ByteArray(Counter.totalSize()) { 0 }, - incTotal: Boolean = true, - result: Int = if (targetCnt.name.startsWith("PASSED")) PASS else DROP - ) { - assertVerdict(version, result, program, pkt, dataRegion) - cntMap[targetCnt] = cntMap.getOrDefault(targetCnt, 0) + 1 - if (incTotal) { - cntMap[TOTAL_PACKETS] = cntMap.getOrDefault(TOTAL_PACKETS, 0) + 1 - } - val errMsg = "Counter is not increased properly. To debug: \n" + - " apf_run --program ${HexDump.toHexString(program)} " + - "--packet ${HexDump.toHexString(pkt)} " + - "--data ${HexDump.toHexString(dataRegion)} --age 0 " + - "${if (version == APF_VERSION_6) "--v6" else "" } --trace | less \n" - assertEquals(cntMap, decodeCountersIntoMap(dataRegion), errMsg) - } - - private fun decodeCountersIntoMap(counterBytes: ByteArray): Map<Counter, Long> { - val counters = Counter::class.java.enumConstants - val ret = HashMap<Counter, Long>() - val skippedCounters = setOf(APF_PROGRAM_ID, APF_VERSION) - // starting from index 2 to skip the endianness mark - for (c in listOf(*counters).subList(2, counters.size)) { - if (c in skippedCounters) continue - val value = ApfCounterTracker.getCounterValue(counterBytes, c) - if (value != 0L) { - ret[c] = value - } - } - return ret - } - private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte { val immLengthEncoding = if (immLength == 4) 3 else immLength return opcode.shl(3).or(immLengthEncoding.shl(1)).or(register).toByte() @@ -2360,15 +1634,4 @@ ) return this.drop(7).toByteArray() } - - private fun getDefaultConfig(apfVersion: Int = APF_VERSION_6): ApfFilter.ApfConfiguration { - val config = ApfFilter.ApfConfiguration() - config.apfCapabilities = - ApfCapabilities(apfVersion, 4096, ARPHRD_ETHER) - config.multicastFilter = false - config.ieee802_3Filter = false - config.ethTypeBlackList = IntArray(0) - config.shouldHandleArpOffload = true - return config - } }
diff --git a/tests/unit/src/android/net/apf/ApfStandaloneTest.kt b/tests/unit/src/android/net/apf/ApfStandaloneTest.kt index 1a2307d..531c4cc 100644 --- a/tests/unit/src/android/net/apf/ApfStandaloneTest.kt +++ b/tests/unit/src/android/net/apf/ApfStandaloneTest.kt
@@ -54,6 +54,8 @@ class ApfStandaloneTest { private val etherTypeDenyList = listOf(0x88A2, 0x88A4, 0x88B8, 0x88CD, 0x88E1, 0x88E3) + private val ramSize = 1024 + private val clampSize = 1024 fun runApfTest(isSuspendMode: Boolean) { val program = generateApfV4Program(isSuspendMode) @@ -248,7 +250,7 @@ val endOfDhcpFilter = "endOfDhcpFilter" val endOfRsFilter = "endOfRsFiler" val endOfPingFilter = "endOfPingFilter" - val gen = ApfV4Generator(APF_VERSION_4) + val gen = ApfV4Generator(APF_VERSION_4, ramSize, clampSize) maybeSetupCounter(gen, Counter.TOTAL_PACKETS) gen.addLoadData(R0, 0)
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index 05e2e39..c80aaa9 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -17,7 +17,10 @@ package android.net.apf; import static android.net.apf.ApfCounterTracker.Counter.getCounterEnumFromOffset; +import static android.net.apf.ApfTestHelpers.consumeInstalledProgram; import static android.net.apf.BaseApfGenerator.APF_VERSION_3; +import static android.net.apf.BaseApfGenerator.APF_VERSION_4; +import static android.net.apf.BaseApfGenerator.APF_VERSION_6; import static android.net.apf.BaseApfGenerator.DROP_LABEL; import static android.net.apf.BaseApfGenerator.MemorySlot; import static android.net.apf.BaseApfGenerator.PASS_LABEL; @@ -32,7 +35,7 @@ import static android.net.apf.ApfTestUtils.assertProgramEquals; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; -import static android.system.OsConstants.ARPHRD_ETHER; +import static android.system.OsConstants.AF_UNIX; import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; @@ -40,24 +43,26 @@ import static android.system.OsConstants.IPPROTO_IPV6; 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.HexDump.toHexString; 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.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.clearInvocations; 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.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.BroadcastReceiver; import android.content.Context; @@ -71,14 +76,15 @@ import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfCounterTracker.Counter; import android.net.apf.ApfFilter.ApfConfiguration; -import android.net.apf.ApfTestUtils.MockIpClientCallback; -import android.net.apf.ApfTestUtils.TestApfFilter; import android.net.apf.BaseApfGenerator.IllegalInstructionException; +import android.net.ip.IpClient; import android.net.metrics.IpConnectivityLog; import android.os.Build; import android.os.PowerManager; +import android.os.SystemClock; import android.stats.connectivity.NetworkQuirkEvent; import android.system.ErrnoException; +import android.system.Os; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -93,6 +99,7 @@ import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.DnsPacket; import com.android.net.module.util.Inet4AddressUtils; +import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.PacketBuilder; import com.android.networkstack.metrics.ApfSessionInfoMetrics; @@ -103,6 +110,7 @@ import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; +import libcore.io.IoUtils; import libcore.io.Streams; import org.junit.After; @@ -118,6 +126,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -144,6 +153,8 @@ @SmallTest public class ApfTest { private static final int APF_VERSION_2 = 2; + private int mRamSize = 1024; + private int mClampSize = 1024; @Rule public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); @@ -164,12 +175,15 @@ @Mock private NetworkQuirkMetrics mNetworkQuirkMetrics; @Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics; @Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics; - @Mock private ApfFilter.Clock mClock; + @Mock private IpClient.IpClientCallbacksWrapper mIpClientCb; @GuardedBy("mApfFilterCreated") private final ArrayList<AndroidPacketFilter> mApfFilterCreated = new ArrayList<>(); @GuardedBy("mThreadsToBeCleared") private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>(); + private FileDescriptor mWriteSocket; + private long mCurrentTimeMs; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -188,13 +202,20 @@ } return null; }).when(mDependencies).onThreadCreated(any()); + FileDescriptor readSocket = new FileDescriptor(); + mWriteSocket = new FileDescriptor(); + Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); + doReturn(readSocket).when(mDependencies).createRaReaderSocket(anyInt()); + mCurrentTimeMs = SystemClock.elapsedRealtime(); + doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); + doReturn(true).when(mIpClientCb).installPacketFilter(any()); } private void quitThreads() throws Exception { ConcurrentUtils.quitThreads( THREAD_QUIT_MAX_RETRY_COUNT, false /* interrupt */, - HANDLER_TIMEOUT_MS, + TIMEOUT_MS, () -> { synchronized (mThreadsToBeCleared) { final ArrayList<Thread> ret = new ArrayList<>(mThreadsToBeCleared); @@ -230,12 +251,12 @@ shutdownApfFilters(); // Clear mocks to prevent from stubs holding instances and cause memory leaks. Mockito.framework().clearInlineMocks(); + IoUtils.closeQuietly(mWriteSocket); + mWriteSocket = null; } private static final String TAG = "ApfTest"; // Expected return codes from APF interpreter. - private static final ApfCapabilities MOCK_APF_CAPABILITIES = - new ApfCapabilities(2, 4096, ARPHRD_ETHER); private static final boolean DROP_MULTICAST = true; private static final boolean ALLOW_MULTICAST = false; @@ -246,7 +267,8 @@ 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 TIMEOUT_MS = 1000; + private static final int NO_CALLBACK_TIMEOUT_MS = 500; private static final int THREAD_QUIT_MAX_RETRY_COUNT = 3; // Constants for opcode encoding @@ -259,9 +281,15 @@ private static final byte SIZE32 = (byte)(3 << 1); private static final byte R1_REG = 1; + private static final byte[] TEST_MAC_ADDR = {2, 3, 4, 5, 6, 7}; + private static final int TEST_IFACE_IDX = 1234; + private static final InterfaceParams TEST_PARAMS = new InterfaceParams("lo", TEST_IFACE_IDX, + MacAddress.fromBytes(TEST_MAC_ADDR), 1500 /* defaultMtu */); + private static ApfConfiguration getDefaultConfig() { ApfFilter.ApfConfiguration config = new ApfConfiguration(); - config.apfCapabilities = MOCK_APF_CAPABILITIES; + config.apfVersionSupported = 2; + config.apfRamSize = 4096; config.multicastFilter = ALLOW_MULTICAST; config.ieee802_3Filter = ALLOW_802_3_FRAMES; config.ethTypeBlackList = new int[0]; @@ -336,17 +364,17 @@ // Empty program should pass because having the program counter reach the // location immediately after the program indicates the packet should be // passed to the AP. - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); assertPass(gen); // Test pass opcode - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addPass(); gen.addJump(DROP_LABEL); assertPass(gen); // Test jumping to pass label. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJump(PASS_LABEL); byte[] program = gen.generate(); assertEquals(1, program.length); @@ -354,7 +382,7 @@ assertPass(program, new byte[MIN_PKT_SIZE], 0); // Test jumping to drop label. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJump(DROP_LABEL); program = gen.generate(); assertEquals(2, program.length); @@ -363,127 +391,127 @@ assertDrop(program, new byte[15], 15); // Test jumping if equal to 0. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test jumping if not equal to 0. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0NotEquals(0, DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0NotEquals(0, DROP_LABEL); assertDrop(gen); // Test jumping if registers equal. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0EqualsR1(DROP_LABEL); assertDrop(gen); // Test jumping if registers not equal. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0NotEqualsR1(DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0NotEqualsR1(DROP_LABEL); assertDrop(gen); // Test load immediate. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test add. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addAdd(1234567890); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test add with a small signed negative value. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addAdd(-1); gen.addJumpIfR0Equals(-1, DROP_LABEL); assertDrop(gen); // Test subtract. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addAdd(-1234567890); gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test or. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addOr(1234567890); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test and. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addAnd(123456789); gen.addJumpIfR0Equals(1234567890 & 123456789, DROP_LABEL); assertDrop(gen); // Test left shift. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLeftShift(1); gen.addJumpIfR0Equals(1234567890 << 1, DROP_LABEL); assertDrop(gen); // Test right shift. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addRightShift(1); gen.addJumpIfR0Equals(1234567890 >> 1, DROP_LABEL); assertDrop(gen); // Test multiply. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 123456789); gen.addMul(2); gen.addJumpIfR0Equals(123456789 * 2, DROP_LABEL); assertDrop(gen); // Test divide. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addDiv(2); gen.addJumpIfR0Equals(1234567890 / 2, DROP_LABEL); assertDrop(gen); // Test divide by zero. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addDiv(0); gen.addJump(DROP_LABEL); assertPass(gen); // Test add. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addAddR1ToR0(); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test subtract. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, -1234567890); gen.addAddR1ToR0(); gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test or. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addOrR0WithR1(); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test and. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 123456789); gen.addAndR0WithR1(); @@ -491,7 +519,7 @@ assertDrop(gen); // Test left shift. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 1); gen.addLeftShiftR0ByR1(); @@ -499,7 +527,7 @@ assertDrop(gen); // Test right shift. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, -1); gen.addLeftShiftR0ByR1(); @@ -507,7 +535,7 @@ assertDrop(gen); // Test multiply. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 123456789); gen.addLoadImmediate(R1, 2); gen.addMulR0ByR1(); @@ -515,7 +543,7 @@ assertDrop(gen); // Test divide. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 2); gen.addDivR0ByR1(); @@ -523,136 +551,136 @@ assertDrop(gen); // Test divide by zero. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addDivR0ByR1(); gen.addJump(DROP_LABEL); assertPass(gen); // Test byte load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad8(R0, 1); gen.addJumpIfR0Equals(45, DROP_LABEL); assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test out of bounds load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad8(R0, 16); gen.addJumpIfR0Equals(0, DROP_LABEL); assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test half-word load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad16(R0, 1); gen.addJumpIfR0Equals((45 << 8) | 67, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test word load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoad32(R0, 1); gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0); // Test byte indexed load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addLoad8Indexed(R0, 0); gen.addJumpIfR0Equals(45, DROP_LABEL); assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test out of bounds indexed load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 8); gen.addLoad8Indexed(R0, 8); gen.addJumpIfR0Equals(0, DROP_LABEL); assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test half-word indexed load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addLoad16Indexed(R0, 0); gen.addJumpIfR0Equals((45 << 8) | 67, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test word indexed load. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addLoad32Indexed(R0, 0); gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0); // Test jumping if greater than. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0GreaterThan(0, DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0GreaterThan(0, DROP_LABEL); assertDrop(gen); // Test jumping if less than. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0LessThan(0, DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0LessThan(1, DROP_LABEL); assertDrop(gen); // Test jumping if any bits set. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertDrop(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 3); gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertDrop(gen); // Test jumping if register greater than. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0GreaterThanR1(DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 2); gen.addLoadImmediate(R1, 1); gen.addJumpIfR0GreaterThanR1(DROP_LABEL); assertDrop(gen); // Test jumping if register less than. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfR0LessThanR1(DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1); gen.addJumpIfR0LessThanR1(DROP_LABEL); assertDrop(gen); // Test jumping if any bits set in register. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 3); gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertPass(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 3); gen.addLoadImmediate(R0, 1); gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertDrop(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 3); gen.addLoadImmediate(R0, 3); gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertDrop(gen); // Test load from memory. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.SLOT_0); gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test store to memory. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addStoreToMemory(MemorySlot.RAM_LEN, R1); gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN); @@ -660,63 +688,63 @@ assertDrop(gen); // Test filter age pre-filled memory. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS); gen.addJumpIfR0Equals(123, DROP_LABEL); assertDrop(gen, new byte[MIN_PKT_SIZE], 123); // Test packet size pre-filled memory. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE); gen.addJumpIfR0Equals(MIN_PKT_SIZE, DROP_LABEL); assertDrop(gen); // Test IPv4 header size pre-filled memory. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE); gen.addJumpIfR0Equals(20, DROP_LABEL); assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,8,0,0x45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test not. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addNot(R0); gen.addJumpIfR0Equals(~1234567890, DROP_LABEL); assertDrop(gen); // Test negate. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addNeg(R0); gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test move. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addMove(R0); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addMove(R1); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test swap. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1234567890); gen.addSwap(); gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1234567890); gen.addSwap(); gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test jump if bytes not equal. - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); program = gen.generate(); @@ -728,20 +756,20 @@ assertEquals(1, program[4]); assertEquals(123, program[5]); assertDrop(program, new byte[MIN_PKT_SIZE], 0); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0}; assertPass(gen, packet123, 0); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); assertDrop(gen, packet123, 0); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, DROP_LABEL); byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0}; assertDrop(gen, packet12345, 0); - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R0, 1); gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, DROP_LABEL); assertPass(gen, packet12345, 0); @@ -750,12 +778,12 @@ @Test(expected = ApfV4Generator.IllegalInstructionException.class) public void testApfGeneratorWantsV2OrGreater() throws Exception { // The minimum supported APF version is 2. - new ApfV4Generator(1); + new ApfV4Generator(1, mRamSize, mClampSize); } @Test public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); try { gen.addStoreData(R0, 0); fail(); @@ -778,22 +806,22 @@ ApfV4Generator gen; // 0-byte immediate: li R0, 0 - gen = new ApfV4Generator(4); + gen = new ApfV4Generator(APF_VERSION_4, mRamSize, mClampSize); gen.addLoadImmediate(R0, 0); assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate()); // 1-byte immediate: li R0, 42 - gen = new ApfV4Generator(4); + gen = new ApfV4Generator(APF_VERSION_4, mRamSize, mClampSize); gen.addLoadImmediate(R0, 42); assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate()); // 2-byte immediate: li R1, 0x1234 - gen = new ApfV4Generator(4); + gen = new ApfV4Generator(APF_VERSION_4, mRamSize, mClampSize); gen.addLoadImmediate(R1, 0x1234); assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1_REG, 0x12, 0x34}, gen.generate()); // 4-byte immediate: li R0, 0x12345678 - gen = new ApfV4Generator(3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 0x12345678); assertProgramEquals( new byte[]{LI_OP | SIZE32, 0x12, 0x34, 0x56, 0x78}, @@ -808,18 +836,18 @@ ApfV4Generator gen; // 1-byte negative immediate: li R0, -42 - gen = new ApfV4Generator(3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, -42); assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate()); // 2-byte negative immediate: li R1, -0x1122 - gen = new ApfV4Generator(3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R1, -0x1122); assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // 4-byte negative immediate: li R0, -0x11223344 - gen = new ApfV4Generator(3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, -0x11223344); assertProgramEquals( new byte[]{LI_OP | SIZE32, (byte)0xEE, (byte)0xDD, (byte)0xCC, (byte)0xBC}, @@ -834,23 +862,23 @@ ApfV4Generator gen; // Load data with no offset: lddw R0, [0 + r1] - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadData(R0, 0); assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate()); // Store data with 8bit negative offset: lddw r0, [-42 + r1] - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addStoreData(R0, -42); assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate()); // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0] - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addStoreData(R1, -0x1122); assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0] - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadData(R1, 0xDEADBEEF); assertProgramEquals( new byte[]{LDDW_OP | SIZE32 | R1_REG, @@ -868,12 +896,12 @@ byte[] expected_data = data.clone(); // No memory access instructions: should leave the data segment untouched. - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Expect value 0x87654321 to be stored starting from address -11 from the end of the // data buffer, in big-endian order. - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 0x87654321); gen.addLoadImmediate(R1, -5); gen.addStoreData(R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16) @@ -890,7 +918,7 @@ @Test public void testApfDataRead() throws IllegalInstructionException, Exception { // Program that DROPs if address 10 (-6) contains 0x87654321. - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R1, 1000); gen.addLoadData(R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16) gen.addJumpIfR0Equals(0x87654321, DROP_LABEL); @@ -919,7 +947,7 @@ */ @Test public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R1, -22); gen.addLoadData(R0, 0); // Load from address 32 -22 + 0 = 10 gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733 @@ -946,7 +974,7 @@ byte[] expected_data = data; // Program that DROPs unconditionally. This is our the baseline. - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 3); gen.addLoadData(R1, 7); gen.addJump(DROP_LABEL); @@ -956,7 +984,7 @@ // 3 instructions, all normal opcodes (LI, LDDW, JMP) with 1 byte immediate = 6 byte program // 32 byte data length, for a total of 38 byte ram len. // APFv6 needs to round this up to be a multiple of 4, so 40. - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); if (mApfVersion == 4) { gen.addLoadData(R1, 15); // R0(20)+15+U32[0..3] >= 6 prog + 32 data, so invalid @@ -967,21 +995,21 @@ assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Subtracting an immediate should work... - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -4); gen.addJump(DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...and underflowing simply wraps around to the end of the buffer... - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -30); gen.addJump(DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...but doesn't allow accesses before the start of the buffer - gen = new ApfV4Generator(APF_VERSION_3); + gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -1000); gen.addJump(DROP_LABEL); // Not reached. @@ -1006,6 +1034,11 @@ } } + private void pretendPacketReceived(byte[] packet) + throws IOException, ErrnoException { + Os.write(mWriteSocket, packet, 0, packet.length); + } + /** * Generate APF program, run pcap file though APF filter, then check all the packets in the file * should be dropped. @@ -1014,20 +1047,20 @@ 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(); - ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER); - config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES; + config.apfVersionSupported = 4; + config.apfRamSize = 1700; config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, + mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 2 /* installCnt */); apfFilter.setLinkProperties(lp); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); byte[] data = new byte[Counter.totalSize()]; final boolean result; @@ -1193,18 +1226,18 @@ @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; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, + mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); apfFilter.setLinkProperties(lp); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); ByteBuffer packet = ByteBuffer.wrap(new byte[100]); if (SdkLevel.isAtLeastV()) { @@ -1216,7 +1249,7 @@ } // Verify unicast IPv4 packet is passed - put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR); + put(packet, ETH_DEST_ADDR_OFFSET, TEST_MAC_ADDR); packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR); assertPass(program, packet.array()); @@ -1244,21 +1277,20 @@ assertDrop(program, packet.array()); // Verify broadcast IPv4 DHCP to us is passed - put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR); + put(packet, DHCP_CLIENT_MAC_OFFSET, TEST_MAC_ADDR); assertPass(program, packet.array()); // Verify unicast IPv4 DHCP to us is passed - put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR); + put(packet, ETH_DEST_ADDR_OFFSET, TEST_MAC_ADDR); assertPass(program, packet.array()); } @Test public void testApfFilterIPv6() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify empty IPv6 packet is passed ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP); @@ -1473,12 +1505,12 @@ @Test public void testAddNopAddsOneByte() throws Exception { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addNop(); assertEquals(1, gen.generate().length); final int count = 42; - gen = new ApfV4Generator(APF_VERSION_2); + gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); for (int i = 0; i < count; i++) { gen.addNop(); } @@ -1486,7 +1518,7 @@ } private ApfV4Generator generateDnsFilter(boolean ipv6, String... labels) throws Exception { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_2, mRamSize, mClampSize); gen.addLoadImmediate(R1, ipv6 ? IPV6_HEADER_LEN : IPV4_HEADER_LEN); DnsUtils.generateFilter(gen, labels); return gen; @@ -1664,18 +1696,18 @@ 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; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); apfFilter.setLinkProperties(lp); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Construct IPv4 and IPv6 multicast packets. ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP); @@ -1697,7 +1729,7 @@ // Construct IPv4 broadcast with L2 unicast address packet (b/30231088). ByteBuffer bcastv4unicastl2packet = makeIpv4Packet(IPPROTO_UDP); - bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR); + bcastv4unicastl2packet.put(TEST_MAC_ADDR); bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr); @@ -1709,9 +1741,8 @@ assertPass(program, bcastv4unicastl2packet.array()); // Turn on multicast filter and verify it works - ipClientCallback.resetApfProgramWait(); apfFilter.setMulticastFilter(true); - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, mcastv4packet.array()); assertDrop(program, mcastv6packet.array()); assertDrop(program, bcastv4packet1.array()); @@ -1719,9 +1750,8 @@ assertDrop(program, bcastv4unicastl2packet.array()); // Turn off multicast filter and verify it's off - ipClientCallback.resetApfProgramWait(); apfFilter.setMulticastFilter(false); - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertPass(program, mcastv4packet.array()); assertPass(program, mcastv6packet.array()); assertPass(program, bcastv4packet1.array()); @@ -1729,13 +1759,14 @@ 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 TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, - mDependencies); + clearInvocations(mIpClientCb); + apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); apfFilter.setLinkProperties(lp); - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, mcastv4packet.array()); assertDrop(program, mcastv6packet.array()); assertDrop(program, bcastv4packet1.array()); @@ -1757,32 +1788,14 @@ doTestApfFilterMulticastPingWhileDozing(true /* isLightDozing */); } - @Test - @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) - public void testShouldHandleLightDozeKillSwitch() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration configuration = getDefaultConfig(); - configuration.shouldHandleLightDoze = false; - final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, - configuration, mNetworkQuirkMetrics, mDependencies); - final ArgumentCaptor<BroadcastReceiver> receiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); - final BroadcastReceiver receiver = receiverCaptor.getValue(); - doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); - receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); - assertFalse(apfFilter.isInDozeMode()); - } - private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration configuration = getDefaultConfig(); - configuration.shouldHandleLightDoze = true; - final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, - configuration, mNetworkQuirkMetrics, mDependencies); + final ApfFilter apfFilter = new ApfFilter(mContext, configuration, TEST_PARAMS, + mIpClientCb, mNetworkQuirkMetrics, mDependencies); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); final ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); + verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture()); final BroadcastReceiver receiver = receiverCaptor.getValue(); // Construct a multicast ICMPv6 ECHO request. @@ -1792,7 +1805,7 @@ put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr); // Normally, we let multicast pings alone... - assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); + assertPass(program, packet.array()); if (isLightDozing) { doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); @@ -1801,19 +1814,21 @@ doReturn(true).when(mPowerManager).isDeviceIdleMode(); receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); } + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // ...and even while dozing... - assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); + assertPass(program, 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()); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + assertDrop(program, 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()); + assertPass(program, 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). @@ -1824,17 +1839,17 @@ doReturn(false).when(mPowerManager).isDeviceIdleMode(); receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); } - assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + assertPass(program, packet.array()); } @Test @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void testApfFilter802_3() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify empty packet of 100 zero bytes is passed // Note that eth-type = 0 makes it an IEEE802.3 frame @@ -1850,11 +1865,10 @@ assertPass(program, packet.array()); // Now turn on the filter - ipClientCallback.resetApfProgramWait(); config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify that IEEE802.3 frame is dropped // In this case ethtype is used for payload length @@ -1877,11 +1891,10 @@ final int[] ipv4BlackList = {ETH_P_IP}; final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6}; - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify empty packet of 100 zero bytes is passed // Note that eth-type = 0 makes it an IEEE802.3 frame @@ -1897,11 +1910,10 @@ assertPass(program, packet.array()); // Now add IPv4 to the black list - ipClientCallback.resetApfProgramWait(); config.ethTypeBlackList = ipv4BlackList; - apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify that IPv4 frame will be dropped setIpv4VersionFields(packet); @@ -1912,11 +1924,10 @@ assertPass(program, packet.array()); // Now let us have both IPv4 and IPv6 in the black list - ipClientCallback.resetApfProgramWait(); config.ethTypeBlackList = ipv4Ipv6BlackList; - apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, + apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify that IPv4 frame will be dropped setIpv4VersionFields(packet); @@ -1927,12 +1938,6 @@ assertDrop(program, packet.array()); } - private byte[] getProgram(MockIpClientCallback cb, ApfFilter 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)); @@ -1956,24 +1961,28 @@ @Test public void testApfFilterArp() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify initially ARP request filter is off, and GARP filter is on. - verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS); + verifyArpFilter(program, 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); + apfFilter.setLinkProperties(lp); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + verifyArpFilter(program, DROP); + apfFilter.setLinkProperties(new LinkProperties()); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Inform ApfFilter of loss of IP and verify ARP filtering is off - verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS); + verifyArpFilter(program, PASS); } private static byte[] arpReply(byte[] sip, byte[] tip) { @@ -2015,12 +2024,12 @@ @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; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); byte[] program; final int srcPort = 12345; final int dstPort = 54321; @@ -2049,7 +2058,7 @@ parcel.ack = ackNum; apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); - program = cb.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify IPv4 keepalive ack packet is dropped // src: 10.0.0.6, port: 54321 @@ -2071,6 +2080,7 @@ // Remove IPv4 keepalive filter apfFilter.removeKeepalivePacketFilter(slot1); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); try { // src: 2404:0:0:0:0:0:faf1, port: 12345 @@ -2088,7 +2098,7 @@ ipv6Parcel.ack = ackNum; apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel); - program = cb.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify IPv6 keepalive ack packet is dropped // src: 2404:0:0:0:0:0:faf2, port: 54321 @@ -2111,7 +2121,7 @@ // Verify multiple filters apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel); - program = cb.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 3 /* installCnt */); // Verify IPv4 keepalive ack packet is dropped // src: 10.0.0.6, port: 54321 @@ -2150,8 +2160,6 @@ // TODO: support V6 packets } - program = cb.assertProgramUpdateAndGet(); - // Verify IPv4, IPv6 packets are passed assertPass(program, ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, @@ -2207,12 +2215,12 @@ @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; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); byte[] program; final int srcPort = 1024; final int dstPort = 4500; @@ -2233,7 +2241,7 @@ parcel.dstPort = dstPort; apfFilter.addNattKeepalivePacketFilter(slot1, parcel); - program = cb.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Verify IPv4 keepalive packet is dropped // src: 10.0.0.6, port: 4500 @@ -2458,10 +2466,9 @@ @Test public void testRaToString() throws Exception { - MockIpClientCallback cb = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, - mDependencies); + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); byte[] packet = buildLargeRa(); ApfFilter.Ra ra = apfFilter.new Ra(packet, packet.length); @@ -2511,29 +2518,32 @@ // Test that when ApfFilter is shown the given packet, it generates a program to filter it // for the given lifetime. - private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, - ByteBuffer packet, int lifetime) throws IOException, ErrnoException { + private byte[] verifyRaLifetime(ByteBuffer packet, int lifetime) + throws IOException, ErrnoException { // Verify new program generated if ApfFilter witnesses RA - apfFilter.pretendPacketReceived(packet.array()); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + clearInvocations(mIpClientCb); + pretendPacketReceived(packet.array()); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); verifyRaLifetime(program, packet, lifetime); + return program; } - private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, - ByteBuffer packet) throws IOException, ErrnoException { - apfFilter.pretendPacketReceived(packet.array()); - ipClientCallback.assertNoProgramUpdate(); + private void assertInvalidRa(ByteBuffer packet) + throws IOException, ErrnoException, InterruptedException { + clearInvocations(mIpClientCb); + pretendPacketReceived(packet.array()); + Thread.sleep(NO_CALLBACK_TIMEOUT_MS); + verify(mIpClientCb, never()).installPacketFilter(any()); } @Test public void testApfFilterRa() throws Exception { - MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); final int ROUTER_LIFETIME = 1000; final int PREFIX_VALID_LIFETIME = 200; @@ -2548,7 +2558,7 @@ ByteBuffer basePacket = ByteBuffer.wrap(ra.build()); assertPass(program, basePacket.array()); - verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME); + verifyRaLifetime(basePacket, ROUTER_LIFETIME); ra = new RaPacketBuilder(ROUTER_LIFETIME); // Check that changes are ignored in every byte of the flow label. @@ -2560,7 +2570,7 @@ ra = new RaPacketBuilder(ROUTER_LIFETIME); ra.addZeroLengthOption(); ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(ra.build()); - assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket); + assertInvalidRa(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 @@ -2568,43 +2578,39 @@ 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); + verifyRaLifetime(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); + verifyRaLifetime(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); + verifyRaLifetime(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); + program = verifyRaLifetime(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); + verifyRaLifetime(dnsslOptionPacket, ROUTER_LIFETIME); ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa()); - verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300); + program = verifyRaLifetime(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); @@ -2617,13 +2623,12 @@ @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 TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); final int RA_REACHABLE_TIME = 1800; final int RA_RETRANSMISSION_TIMER = 1234; @@ -2637,8 +2642,8 @@ assertPass(program, raPacket); // Assume apf is shown the given RA, it generates program to filter it. - apfFilter.pretendPacketReceived(raPacket); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(raPacket); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, raPacket); // A packet with different reachable time should be passed. @@ -2659,13 +2664,12 @@ @SuppressWarnings("ByteBufferBackingArray") @Test public void testRaWithProgramInstalledSomeTimeAfterLastSeen() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); final int routerLifetime = 1000; final int timePassedSeconds = 12; @@ -2673,26 +2677,32 @@ // Verify that when the program is generated and installed some time after RA is last seen // it should be installed with the correct remaining lifetime. ByteBuffer basePacket = ByteBuffer.wrap(new RaPacketBuilder(routerLifetime).build()); - verifyRaLifetime(apfFilter, ipClientCallback, basePacket, routerLifetime); - apfFilter.increaseCurrentTimeSeconds(timePassedSeconds); + verifyRaLifetime(basePacket, routerLifetime); + + mCurrentTimeMs += timePassedSeconds * DateUtils.SECOND_IN_MILLIS; + doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { apfFilter.installNewProgramLocked(); } - program = ipClientCallback.assertProgramUpdateAndGet(); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds); // Packet should be passed if the program is installed after 1/6 * lifetime from last seen - apfFilter.increaseCurrentTimeSeconds((int) (routerLifetime / 6) - timePassedSeconds - 1); + mCurrentTimeMs += + ((routerLifetime / 6) - timePassedSeconds - 1) * DateUtils.SECOND_IN_MILLIS; + doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { apfFilter.installNewProgramLocked(); } - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, basePacket.array()); - apfFilter.increaseCurrentTimeSeconds(1); + + mCurrentTimeMs += DateUtils.SECOND_IN_MILLIS; + doReturn(mCurrentTimeMs).when(mDependencies).elapsedRealtime(); synchronized (apfFilter) { apfFilter.installNewProgramLocked(); } - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertPass(program, basePacket.array()); } @@ -2727,12 +2737,11 @@ 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; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, - mDependencies); + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); for (int i = 0; i < 1000; i++) { byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; r.nextBytes(packet); @@ -2749,12 +2758,11 @@ 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; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, - mDependencies); + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); for (int i = 0; i < 1000; i++) { byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; r.nextBytes(packet); @@ -2768,34 +2776,34 @@ @Test public void testMatchedRaUpdatesLifetime() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final TestApfFilter apfFilter = new TestApfFilter(mContext, getDefaultConfig(), - ipClientCallback, mNetworkQuirkMetrics, mDependencies); + final ApfFilter apfFilter = new ApfFilter(mContext, getDefaultConfig(), TEST_PARAMS, + mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // lifetime dropped significantly, assert pass ra = new RaPacketBuilder(200 /* router lifetime */).build(); assertPass(program, ra); // update program with the new RA - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // assert program was updated and new lifetimes were taken into account. assertDrop(program, ra); } - @Test public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - TestApfFilter apfFilter; + ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // 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) @@ -2843,13 +2851,11 @@ final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0"; final List<String> lifetimes = List.of("FFFFFFFF", "00000000", "00000001", "00001B58"); for (String lifetime : lifetimes) { - apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, - mDependencies); 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(); + pretendPacketReceived(ra); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); } } @@ -2857,20 +2863,20 @@ // Old lifetime is 0 @Test public void testAcceptRaMinLftCase1a() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */) .addPioOption(1800 /*valid*/, 0 /*preferred*/, "2001:db8::/64") .build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); @@ -2886,20 +2892,20 @@ // Old lifetime is > 0 @Test public void testAcceptRaMinLftCase2a() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */) .addPioOption(1800 /*valid*/, 100 /*preferred*/, "2001:db8::/64") .build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); @@ -2922,18 +2928,18 @@ // Old lifetime is 0 @Test public void testAcceptRaMinLftCase1b() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(0 /* router lifetime */).build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); @@ -2947,23 +2953,22 @@ assertPass(program, ra); } - // Test for go/apf-ra-filter Case 2b. // Old lifetime is < accept_ra_min_lft (but not 0). @Test public void testAcceptRaMinLftCase2b() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(100 /* router lifetime */).build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); @@ -2989,18 +2994,18 @@ // Old lifetime is >= accept_ra_min_lft and <= 3 * accept_ra_min_lft @Test public void testAcceptRaMinLftCase3b() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(200 /* router lifetime */).build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); @@ -3022,18 +3027,18 @@ // Old lifetime is > 3 * accept_ra_min_lft @Test public void testAcceptRaMinLftCase4b() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // repeated RA is dropped assertDrop(program, ra); @@ -3061,17 +3066,17 @@ @Test public void testRaFilterIsUpdated() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Create an initial RA and build an APF program byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + byte[] program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // repeated RA is dropped. assertDrop(program, ra); @@ -3079,38 +3084,38 @@ // updated RA is passed, repeated RA is dropped after program update. ra = new RaPacketBuilder(599 /* router lifetime */).build(); assertPass(program, ra); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(180 /* router lifetime */).build(); assertPass(program, ra); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(0 /* router lifetime */).build(); assertPass(program, ra); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(180 /* router lifetime */).build(); assertPass(program, ra); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(599 /* router lifetime */).build(); assertPass(program, ra); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, ra); ra = new RaPacketBuilder(1800 /* router lifetime */).build(); assertPass(program, ra); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); assertDrop(program, ra); } @@ -3133,18 +3138,12 @@ assertEquals(want, got); } - private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config, - MockIpClientCallback ipClientCallback) throws Exception { - return new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, - mDependencies, mClock); - } - - @Test public void testInstallPacketFilterFailure() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false); + doReturn(false).when(mIpClientCb).installPacketFilter(any()); final ApfConfiguration config = getDefaultConfig(); - final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); verify(mNetworkQuirkMetrics).statsWrite(); reset(mNetworkQuirkMetrics); @@ -3158,28 +3157,27 @@ @Test public void testApfProgramOverSize() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration config = getDefaultConfig(); - final ApfCapabilities capabilities = new ApfCapabilities(2, 512, ARPHRD_ETHER); - config.apfCapabilities = capabilities; - final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); + config.apfVersionSupported = 2; + config.apfRamSize = 512; + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); final byte[] ra = buildLargeRa(); - apfFilter.pretendPacketReceived(ra); + pretendPacketReceived(ra); // The generated program size will be 529, which is larger than 512 - program = ipClientCallback.assertProgramUpdateAndGet(); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE); verify(mNetworkQuirkMetrics).statsWrite(); } @Test public void testGenerateApfProgramException() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration config = getDefaultConfig(); - final TestAndroidPacketFilter apfFilter; - apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, - mDependencies, true /* throwsExceptionWhenGeneratesProgram */); + ApfFilter apfFilter = spy(new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies)); synchronized (apfFilter) { + when(apfFilter.emitPrologueLocked()).thenThrow(new IllegalStateException("test")); apfFilter.installNewProgramLocked(); } verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION); @@ -3188,17 +3186,17 @@ @Test public void testApfSessionInfoMetrics() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration config = getDefaultConfig(); - final ApfCapabilities capabilities = new ApfCapabilities(4, 4096, ARPHRD_ETHER); - config.apfCapabilities = capabilities; + 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); + doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); + byte[] program = consumeInstalledProgram(mIpClientCb, 2 /* installCnt */); int maxProgramSize = 0; int numProgramUpdated = 0; - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); maxProgramSize = Math.max(maxProgramSize, program.length); numProgramUpdated++; @@ -3215,14 +3213,14 @@ expectedData[totalPacketsCounterIdx + 3] += 1; expectedData[passedIpv6IcmpCounterIdx + 3] += 1; assertDataMemoryContentsIgnoreVersion(PASS, program, ra, data, expectedData); - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); maxProgramSize = Math.max(maxProgramSize, program.length); numProgramUpdated++; apfFilter.setMulticastFilter(true); // setMulticastFilter will trigger program installation. - program = ipClientCallback.assertProgramUpdateAndGet(); + program = consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); maxProgramSize = Math.max(maxProgramSize, program.length); numProgramUpdated++; @@ -3239,7 +3237,7 @@ apfFilter.setDataSnapshot(data); // Write metrics data to statsd pipeline when shutdown. - doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime(); + doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime(); apfFilter.shutdown(); verify(mApfSessionInfoMetrics).setVersion(4); verify(mApfSessionInfoMetrics).setMemorySize(4096); @@ -3268,13 +3266,13 @@ @Test public void testIpClientRaInfoMetrics() 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(); + doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); final int routerLifetime = 1000; final int prefixValidLifetime = 200; @@ -3311,23 +3309,24 @@ // 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.assertProgramUpdateAndGet(); - apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build()); - ipClientCallback.assertProgramUpdateAndGet(); - apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build()); - ipClientCallback.assertProgramUpdateAndGet(); - apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build()); - ipClientCallback.assertProgramUpdateAndGet(); + pretendPacketReceived(ra1.build()); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + pretendPacketReceived(ra2.build()); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + pretendPacketReceived(raInvalid.build()); + Thread.sleep(NO_CALLBACK_TIMEOUT_MS); + verify(mIpClientCb, never()).installPacketFilter(any()); + pretendPacketReceived(raZeroRouterLifetime.build()); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + pretendPacketReceived(raZeroPioValidLifetime.build()); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + pretendPacketReceived(raZeroRdnssLifetime.build()); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); + pretendPacketReceived(raZeroRioRouteLifetime.build()); + consumeInstalledProgram(mIpClientCb, 1 /* installCnt */); // Write metrics data to statsd pipeline when shutdown. - doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime(); + doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime(); apfFilter.shutdown(); // Verify each metric fields in IpClientRaInfoMetrics. @@ -3342,26 +3341,25 @@ } private void verifyNoMetricsWrittenForShortDuration(boolean isLegacy) 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(); + doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); + final ApfFilter apfFilter = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); + doReturn(startTimeMs + durationTimeMs - 1).when(mDependencies).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. - ApfFilter.Clock clock = mock(ApfFilter.Clock.class); - doReturn(startTimeMs).when(clock).elapsedRealtime(); - final TestAndroidPacketFilter apfFilter2 = new TestApfFilter(mContext, config, - ipClientCallback, mNetworkQuirkMetrics, mDependencies, clock); - doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime(); + doReturn(startTimeMs).when(mDependencies).elapsedRealtime(); + final ApfFilter apfFilter2 = new ApfFilter(mContext, config, TEST_PARAMS, mIpClientCb, + mNetworkQuirkMetrics, mDependencies); + doReturn(startTimeMs + durationTimeMs).when(mDependencies).elapsedRealtime(); apfFilter2.shutdown(); verify(mApfSessionInfoMetrics).statsWrite(); verify(mIpClientRaInfoMetrics).statsWrite(); @@ -3388,15 +3386,18 @@ @Test public void testApfGeneratorPropagation() throws IllegalInstructionException { - ApfV4Generator v4Gen = new ApfV4Generator(APF_VERSION_3); - ApfV6Generator v6Gen = new ApfV6Generator(1024); + ApfV4Generator v4Gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, + 1024 /* clampSize */); + ApfV6Generator v6Gen = new ApfV6Generator(APF_VERSION_6, 1024 /* ramSize */, + 1024 /* clampSize */); assertEquals(4, deriveApfGeneratorVersion(v4Gen)); assertEquals(6, deriveApfGeneratorVersion(v6Gen)); } @Test public void testFullApfV4ProgramGenerationIPV6() throws IllegalInstructionException { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, + 1024 /* clampSize */); gen.addLoadImmediate(R1, -4); gen.addLoadData(R0, 0); gen.addAdd(1); @@ -3549,7 +3550,8 @@ @Test public void testFullApfV4ProgramGenerationIPV4() throws IllegalInstructionException { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, + 1024 /* clampSize */); gen.addLoadImmediate(R1, -4); gen.addLoadData(R0, 0); gen.addAdd(1); @@ -3670,7 +3672,7 @@ @Test public void testFullApfV4ProgramGenerationNatTKeepAliveV4() throws IllegalInstructionException { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, true); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, mRamSize, mClampSize, true); gen.addLoadImmediate(R1, -4); gen.addLoadData(R0, 0); gen.addAdd(1); @@ -3785,7 +3787,8 @@ @Test public void testInfiniteLifetimeFullApfV4ProgramGeneration() throws IllegalInstructionException { - ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, true); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, 1024 /* ramSize */, + 1024 /* clampSize */, true); gen.addLoadCounter(R0, getCounterEnumFromOffset(-8)); gen.addAdd(1); gen.addStoreData(R0, 0);
diff --git a/tests/unit/src/android/net/apf/ApfTestHelpers.kt b/tests/unit/src/android/net/apf/ApfTestHelpers.kt new file mode 100644 index 0000000..30d5813 --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfTestHelpers.kt
@@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 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 android.net.apf.ApfCounterTracker.Counter +import android.net.apf.ApfCounterTracker.Counter.APF_PROGRAM_ID +import android.net.apf.ApfCounterTracker.Counter.APF_VERSION +import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS +import android.net.apf.ApfTestUtils.DROP +import android.net.apf.ApfTestUtils.PASS +import android.net.apf.ApfTestUtils.assertVerdict +import android.net.apf.BaseApfGenerator.APF_VERSION_6 +import android.net.ip.IpClient +import com.android.net.module.util.HexDump +import kotlin.test.assertEquals +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify + +class ApfTestHelpers private constructor() { + companion object { + const val TIMEOUT_MS: Long = 1000 + fun verifyProgramRun( + version: Int, + program: ByteArray, + pkt: ByteArray, + targetCnt: Counter, + cntMap: MutableMap<Counter, Long> = mutableMapOf(), + dataRegion: ByteArray = ByteArray(Counter.totalSize()) { 0 }, + incTotal: Boolean = true, + result: Int = if (targetCnt.name.startsWith("PASSED")) PASS else DROP + ) { + assertVerdict(version, result, program, pkt, dataRegion) + cntMap[targetCnt] = cntMap.getOrDefault(targetCnt, 0) + 1 + if (incTotal) { + cntMap[TOTAL_PACKETS] = cntMap.getOrDefault(TOTAL_PACKETS, 0) + 1 + } + val errMsg = "Counter is not increased properly. To debug: \n" + + " apf_run --program ${HexDump.toHexString(program)} " + + "--packet ${HexDump.toHexString(pkt)} " + + "--data ${HexDump.toHexString(dataRegion)} --age 0 " + + "${if (version == APF_VERSION_6) "--v6" else "" } --trace | less \n" + assertEquals(cntMap, decodeCountersIntoMap(dataRegion), errMsg) + } + + fun decodeCountersIntoMap(counterBytes: ByteArray): Map<Counter, Long> { + val counters = Counter::class.java.enumConstants + val ret = HashMap<Counter, Long>() + val skippedCounters = setOf(APF_PROGRAM_ID, APF_VERSION) + // starting from index 2 to skip the endianness mark + if (counters != null) { + for (c in listOf(*counters).subList(2, counters.size)) { + if (c in skippedCounters) continue + val value = ApfCounterTracker.getCounterValue(counterBytes, c) + if (value != 0L) { + ret[c] = value + } + } + } + return ret + } + + @JvmStatic + fun consumeInstalledProgram( + ipClientCb: IpClient.IpClientCallbacksWrapper, + installCnt: Int + ): ByteArray { + val programCaptor = ArgumentCaptor.forClass( + ByteArray::class.java + ) + + verify(ipClientCb, timeout(TIMEOUT_MS).times(installCnt)).installPacketFilter( + programCaptor.capture() + ) + + clearInvocations<Any>(ipClientCb) + return programCaptor.value + } + } +}
diff --git a/tests/unit/src/android/net/apf/ApfTestUtils.java b/tests/unit/src/android/net/apf/ApfTestUtils.java index 0b3ea65..fc9fdef 100644 --- a/tests/unit/src/android/net/apf/ApfTestUtils.java +++ b/tests/unit/src/android/net/apf/ApfTestUtils.java
@@ -16,40 +16,11 @@ package android.net.apf; import static android.net.apf.ApfJniUtils.apfSimulate; -import static android.system.OsConstants.AF_UNIX; -import static android.system.OsConstants.SOCK_STREAM; 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.mock; -import android.content.Context; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.apf.BaseApfGenerator.IllegalInstructionException; -import android.net.ip.IIpClientCallbacks; -import android.net.ip.IpClient; -import android.net.metrics.IpConnectivityLog; -import android.os.ConditionVariable; -import android.os.SystemClock; -import android.system.ErrnoException; -import android.system.Os; -import android.text.format.DateUtils; - -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.SharedLog; -import com.android.networkstack.apishim.NetworkInformationShimImpl; -import com.android.networkstack.metrics.NetworkQuirkMetrics; -import libcore.io.IoUtils; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.InetAddress; import java.util.Arrays; /** @@ -237,270 +208,4 @@ throws ApfV4Generator.IllegalInstructionException { assertVerdict(apfVersion, DROP, gen, new byte[MIN_PKT_SIZE], 0); } - - /** - * The Mock ip client callback class. - */ - public 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 apf filter class. - */ - public static class TestApfFilter extends ApfFilter implements TestAndroidPacketFilter { - public static final byte[] MOCK_MAC_ADDR = {2, 3, 4, 5, 6, 7}; - private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; - - private FileDescriptor mWriteSocket; - private long mCurrentTimeMs = SystemClock.elapsedRealtime(); - private final MockIpClientCallback mMockIpClientCb; - private final boolean mThrowsExceptionWhenGeneratesProgram; - - public TestApfFilter(Context context, ApfConfiguration config, - MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, - Dependencies dependencies) throws Exception { - this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, - false /* throwsExceptionWhenGeneratesProgram */, new ApfFilter.Clock()); - } - - public TestApfFilter(Context context, ApfConfiguration config, - MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, - Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram) - throws Exception { - this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, - throwsExceptionWhenGeneratesProgram, new ApfFilter.Clock()); - } - - public TestApfFilter(Context context, ApfConfiguration config, - MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, - Dependencies dependencies, ApfFilter.Clock clock) throws Exception { - this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, - false /* throwsExceptionWhenGeneratesProgram */, clock); - } - - public TestApfFilter(Context context, ApfConfiguration config, - MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, - Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram, - ApfFilter.Clock clock) throws Exception { - super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, - networkQuirkMetrics, dependencies, clock); - mMockIpClientCb = ipClientCallback; - mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram; - } - - /** - * Create a new test ApfFiler. - */ - public static ApfFilter createTestApfFilter(Context context, - MockIpClientCallback ipClientCallback, ApfConfiguration config, - NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies) - throws Exception { - LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); - LinkProperties lp = new LinkProperties(); - lp.addLinkAddress(link); - TestApfFilter apfFilter = new TestApfFilter(context, config, ipClientCallback, - networkQuirkMetrics, dependencies); - apfFilter.setLinkProperties(lp); - return apfFilter; - } - - /** - * Pretend an RA packet has been received and show it to ApfFilter. - */ - 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); - } - - /** - * Simulate current time changes. - */ - public void increaseCurrentTimeSeconds(int delta) { - mCurrentTimeMs += delta * DateUtils.SECOND_IN_MILLIS; - } - - @Override - protected int secondsSinceBoot() { - return (int) (mCurrentTimeMs / DateUtils.SECOND_IN_MILLIS); - } - - @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 ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException { - if (mThrowsExceptionWhenGeneratesProgram) { - throw new IllegalStateException(); - } - return super.emitPrologueLocked(); - } - } - - /** - * The test legacy apf filter class. - */ - public 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; - - public 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 ApfFilter.Clock()); - } - - public 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 ApfFilter.Clock()); - } - - public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, - NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, - ApfFilter.Clock clock) throws Exception { - this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, - dependencies, false /* throwsExceptionWhenGeneratesProgram */, clock); - } - - public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, - MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, - NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, - boolean throwsExceptionWhenGeneratesProgram, ApfFilter.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 IllegalInstructionException { - if (mThrowsExceptionWhenGeneratesProgram) { - throw new IllegalStateException(); - } - return super.emitPrologueLocked(); - } - } }
diff --git a/tests/unit/src/android/net/apf/Bpf2Apf.java b/tests/unit/src/android/net/apf/Bpf2Apf.java index 49c241e..4dee2f6 100644 --- a/tests/unit/src/android/net/apf/Bpf2Apf.java +++ b/tests/unit/src/android/net/apf/Bpf2Apf.java
@@ -16,6 +16,7 @@ package android.net.apf; +import static android.net.apf.BaseApfGenerator.APF_VERSION_3; import static android.net.apf.BaseApfGenerator.MemorySlot; import static android.net.apf.BaseApfGenerator.Register.R0; import static android.net.apf.BaseApfGenerator.Register.R1; @@ -39,6 +40,8 @@ * android.net.apf.Bpf2Apf */ public class Bpf2Apf { + private static int sRamSize = 1024; + private static int sClampSize = 1024; private static int parseImm(String line, String arg) { if (!arg.startsWith("#0x")) { throw new IllegalArgumentException("Unhandled instruction: " + line); @@ -316,7 +319,7 @@ * program and return it. */ public static byte[] convert(String bpf) throws IllegalInstructionException { - ApfV4Generator gen = new ApfV4Generator(3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, sRamSize, sClampSize); for (String line : bpf.split("\\n")) convertLine(line, gen); return gen.generate(); } @@ -329,7 +332,7 @@ BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line = null; StringBuilder responseData = new StringBuilder(); - ApfV4Generator gen = new ApfV4Generator(3); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_3, sRamSize, sClampSize); while ((line = in.readLine()) != null) convertLine(line, gen); System.out.write(gen.generate()); }
diff --git a/tests/unit/src/android/net/apf/LegacyApfTest.java b/tests/unit/src/android/net/apf/LegacyApfTest.java index cb3fbca..2504604 100644 --- a/tests/unit/src/android/net/apf/LegacyApfTest.java +++ b/tests/unit/src/android/net/apf/LegacyApfTest.java
@@ -19,15 +19,15 @@ import static android.net.apf.ApfJniUtils.dropsAllPackets; import static android.net.apf.ApfTestUtils.DROP; import static android.net.apf.ApfTestUtils.PASS; -import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; -import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED; -import static android.system.OsConstants.ARPHRD_ETHER; +import static android.net.apf.ApfTestUtils.TIMEOUT_MS; +import static android.system.OsConstants.AF_UNIX; 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; @@ -35,7 +35,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -45,9 +45,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -55,14 +53,15 @@ import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfCounterTracker.Counter; import android.net.apf.ApfFilter.ApfConfiguration; -import android.net.apf.ApfTestUtils.MockIpClientCallback; -import android.net.apf.ApfTestUtils.TestApfFilter; -import android.net.apf.ApfTestUtils.TestLegacyApfFilter; +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; @@ -72,8 +71,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.HexDump; -import com.android.modules.utils.build.SdkLevel; +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; @@ -82,6 +83,7 @@ import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; +import libcore.io.IoUtils; import libcore.io.Streams; import org.junit.After; @@ -97,6 +99,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -209,9 +212,6 @@ } private static final String TAG = "ApfTest"; - // Expected return codes from APF interpreter. - private static final ApfCapabilities MOCK_APF_CAPABILITIES = - new ApfCapabilities(2, 4096, ARPHRD_ETHER); private static final boolean DROP_MULTICAST = true; private static final boolean ALLOW_MULTICAST = false; @@ -237,7 +237,8 @@ private static ApfConfiguration getDefaultConfig() { ApfFilter.ApfConfiguration config = new ApfConfiguration(); - config.apfCapabilities = MOCK_APF_CAPABILITIES; + config.apfVersionSupported = 2; + config.apfRamSize = 4096; config.multicastFilter = ALLOW_MULTICAST; config.ieee802_3Filter = ALLOW_802_3_FRAMES; config.ethTypeBlackList = new int[0]; @@ -316,12 +317,12 @@ lp.addLinkAddress(link); ApfConfiguration config = getDefaultConfig(); - ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER); - config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES; + config.apfVersionSupported = 4; + config.apfRamSize = 1700; config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); apfFilter.setLinkProperties(lp); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); byte[] data = new byte[Counter.totalSize()]; @@ -496,23 +497,18 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + 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]); - if (SdkLevel.isAtLeastV()) { - // Verify empty packet of 100 zero bytes is dropped - assertDrop(program, packet.array()); - } else { - // Verify empty packet of 100 zero bytes is passed - assertPass(program, packet.array()); - } + // 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, TestApfFilter.MOCK_MAC_ADDR); + 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()); @@ -540,11 +536,11 @@ assertDrop(program, packet.array()); // Verify broadcast IPv4 DHCP to us is passed - put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR); + 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, TestApfFilter.MOCK_MAC_ADDR); + put(packet, ETH_DEST_ADDR_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR); assertPass(program, packet.array()); } @@ -552,8 +548,8 @@ public void testApfFilterIPv6() throws Exception { MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty IPv6 packet is passed @@ -601,8 +597,8 @@ ApfConfiguration config = getDefaultConfig(); config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); apfFilter.setLinkProperties(lp); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); @@ -627,7 +623,7 @@ // Construct IPv4 broadcast with L2 unicast address packet (b/30231088). ByteBuffer bcastv4unicastl2packet = makeIpv4Packet(IPPROTO_UDP); - bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR); + bcastv4unicastl2packet.put(TestLegacyApfFilter.MOCK_MAC_ADDR); bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr); @@ -662,8 +658,8 @@ ipClientCallback.resetApfProgramWait(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, - mDependencies); + apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); apfFilter.setLinkProperties(lp); program = ipClientCallback.assertProgramUpdateAndGet(); assertDrop(program, mcastv4packet.array()); @@ -678,60 +674,22 @@ @Test public void testApfFilterMulticastPingWhileDozing() throws Exception { - doTestApfFilterMulticastPingWhileDozing(false /* isLightDozing */); - } - - @Test - @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) - public void testApfFilterMulticastPingWhileLightDozing() throws Exception { - doTestApfFilterMulticastPingWhileDozing(true /* isLightDozing */); - } - - @Test - @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) - public void testShouldHandleLightDozeKillSwitch() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); + MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration configuration = getDefaultConfig(); - configuration.shouldHandleLightDoze = false; - final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, - configuration, mNetworkQuirkMetrics, mDependencies); - final ArgumentCaptor<BroadcastReceiver> receiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); - final BroadcastReceiver receiver = receiverCaptor.getValue(); - doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); - receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); - assertFalse(apfFilter.isInDozeMode()); - } - - private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration configuration = getDefaultConfig(); - configuration.shouldHandleLightDoze = true; - final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, - configuration, mNetworkQuirkMetrics, mDependencies); - final ArgumentCaptor<BroadcastReceiver> receiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean()); - final BroadcastReceiver receiver = receiverCaptor.getValue(); + 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}; - final ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6); + 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()); - if (isLightDozing) { - doReturn(true).when(mPowerManager).isDeviceLightIdleMode(); - receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); - } else { - doReturn(true).when(mPowerManager).isDeviceIdleMode(); - receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); - } // ...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. @@ -747,14 +705,10 @@ // Now wake up from doze mode to ensure that we no longer drop the packets. // (The multicast filter is still enabled at this point). - if (isLightDozing) { - doReturn(false).when(mPowerManager).isDeviceLightIdleMode(); - receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED)); - } else { - doReturn(false).when(mPowerManager).isDeviceIdleMode(); - receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED)); - } + apfFilter.setDozeMode(false); assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array()); + + apfFilter.shutdown(); } @Test @@ -762,8 +716,8 @@ public void testApfFilter802_3() throws Exception { MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty packet of 100 zero bytes is passed @@ -782,8 +736,8 @@ // Now turn on the filter ipClientCallback.resetApfProgramWait(); config.ieee802_3Filter = DROP_802_3_FRAMES; - apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mNetworkQuirkMetrics, mDependencies); + apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IEEE802.3 frame is dropped @@ -809,8 +763,8 @@ MockIpClientCallback ipClientCallback = new MockIpClientCallback(); ApfConfiguration config = getDefaultConfig(); - ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); // Verify empty packet of 100 zero bytes is passed @@ -829,8 +783,8 @@ // Now add IPv4 to the black list ipClientCallback.resetApfProgramWait(); config.ethTypeBlackList = ipv4BlackList; - apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mNetworkQuirkMetrics, mDependencies); + apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IPv4 frame will be dropped @@ -844,8 +798,8 @@ // Now let us have both IPv4 and IPv6 in the black list ipClientCallback.resetApfProgramWait(); config.ethTypeBlackList = ipv4Ipv6BlackList; - apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config, - mNetworkQuirkMetrics, mDependencies); + apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); program = ipClientCallback.assertProgramUpdateAndGet(); // Verify that IPv4 frame will be dropped @@ -857,7 +811,8 @@ assertDrop(program, packet.array()); } - private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) { + private byte[] getProgram(MockIpClientCallback cb, TestLegacyApfFilter filter, + LinkProperties lp) { cb.resetApfProgramWait(); filter.setLinkProperties(lp); return cb.assertProgramUpdateAndGet(); @@ -867,7 +822,7 @@ // Verify ARP request packet assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR)); assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR)); - assertVerdict(filterResult, program, arpRequestBroadcast(IPV4_ANY_HOST_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)); @@ -890,8 +845,8 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + 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); @@ -949,8 +904,8 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, cb, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); byte[] program; final int srcPort = 12345; final int dstPort = 54321; @@ -1141,8 +1096,8 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, cb, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); byte[] program; final int srcPort = 1024; final int dstPort = 4500; @@ -1424,16 +1379,18 @@ // Test that when ApfFilter is shown the given packet, it generates a program to filter it // for the given lifetime. - private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, - ByteBuffer packet, int lifetime) throws IOException, ErrnoException { + 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(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback, - ByteBuffer packet) throws IOException, ErrnoException { + private void assertInvalidRa(TestLegacyApfFilter apfFilter, + MockIpClientCallback ipClientCallback, ByteBuffer packet) + throws IOException, ErrnoException { apfFilter.pretendPacketReceived(packet.array()); ipClientCallback.assertNoProgramUpdate(); } @@ -1444,8 +1401,8 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, + mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); final int ROUTER_LIFETIME = 1000; @@ -1534,8 +1491,8 @@ final ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); + 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; @@ -1568,47 +1525,6 @@ assertPass(program, raPacket); } - // The ByteBuffer is always created by ByteBuffer#wrap in the helper functions - @SuppressWarnings("ByteBufferBackingArray") - @Test - public void testRaWithProgramInstalledSomeTimeAfterLastSeen() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final ApfConfiguration config = getDefaultConfig(); - config.multicastFilter = DROP_MULTICAST; - config.ieee802_3Filter = DROP_802_3_FRAMES; - final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, - mNetworkQuirkMetrics, mDependencies); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - final int routerLifetime = 1000; - final int timePassedSeconds = 12; - - // Verify that when the program is generated and installed some time after RA is last seen - // it should be installed with the correct remaining lifetime. - ByteBuffer basePacket = ByteBuffer.wrap(new RaPacketBuilder(routerLifetime).build()); - verifyRaLifetime(apfFilter, ipClientCallback, basePacket, routerLifetime); - apfFilter.increaseCurrentTimeSeconds(timePassedSeconds); - synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); - } - program = ipClientCallback.assertProgramUpdateAndGet(); - verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds); - - // Packet should be passed if the program is installed after 1/6 * lifetime from last seen - apfFilter.increaseCurrentTimeSeconds((int) (routerLifetime / 6) - timePassedSeconds - 1); - synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); - } - program = ipClientCallback.assertProgramUpdateAndGet(); - assertDrop(program, basePacket.array()); - apfFilter.increaseCurrentTimeSeconds(1); - synchronized (apfFilter) { - apfFilter.installNewProgramLocked(); - } - program = ipClientCallback.assertProgramUpdateAndGet(); - assertPass(program, basePacket.array()); - } - /** * 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. @@ -1644,14 +1560,14 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, - mDependencies); + 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 (ApfFilter.InvalidRaException e) { + } catch (LegacyApfFilter.InvalidRaException e) { } catch (Exception e) { throw new Exception("bad packet: " + HexDump.toHexString(packet), e); } @@ -1666,8 +1582,8 @@ ApfConfiguration config = getDefaultConfig(); config.multicastFilter = DROP_MULTICAST; config.ieee802_3Filter = DROP_802_3_FRAMES; - TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics, - mDependencies); + 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); @@ -1680,35 +1596,12 @@ } @Test - public void testMatchedRaUpdatesLifetime() throws Exception { - final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); - final TestApfFilter apfFilter = new TestApfFilter(mContext, getDefaultConfig(), - ipClientCallback, mNetworkQuirkMetrics, mDependencies); - - // Create an RA and build an APF program - byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build(); - apfFilter.pretendPacketReceived(ra); - byte[] program = ipClientCallback.assertProgramUpdateAndGet(); - - // lifetime dropped significantly, assert pass - ra = new RaPacketBuilder(200 /* router lifetime */).build(); - assertPass(program, ra); - - // update program with the new RA - apfFilter.pretendPacketReceived(ra); - program = ipClientCallback.assertProgramUpdateAndGet(); - - // assert program was updated and new lifetimes were taken into account. - assertDrop(program, ra); - } - - @Test public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception { final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); // configure accept_ra_min_lft final ApfConfiguration config = getDefaultConfig(); config.acceptRaMinLft = 180; - TestApfFilter apfFilter; + 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) @@ -1754,10 +1647,10 @@ // Reserved // Advertisement Interval: 600000 final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0"; - final List<String> lifetimes = List.of("FFFFFFFF", "00000000", "00000001", "00001B58"); + final List<String> lifetimes = List.of("FFFFFFFF", "00000001", "00001B58"); for (String lifetime : lifetimes) { - apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics, - mDependencies); + 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. @@ -1792,8 +1685,8 @@ public void testApfProgramOverSize_LegacyApfFilter() throws Exception { final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration config = getDefaultConfig(); - final ApfCapabilities capabilities = new ApfCapabilities(2, 512, ARPHRD_ETHER); - config.apfCapabilities = capabilities; + config.apfVersionSupported = 2; + config.apfRamSize = 512; final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback); byte[] program = ipClientCallback.assertProgramUpdateAndGet(); final byte[] ra = buildLargeRa(); @@ -1823,8 +1716,8 @@ public void testApfSessionInfoMetrics_LegacyApfFilter() throws Exception { final MockIpClientCallback ipClientCallback = new MockIpClientCallback(); final ApfConfiguration config = getDefaultConfig(); - final ApfCapabilities capabilities = new ApfCapabilities(4, 4096, ARPHRD_ETHER); - config.apfCapabilities = capabilities; + config.apfVersionSupported = 4; + config.apfRamSize = 4096; final long startTimeMs = 12345; final long durationTimeMs = config.minMetricsSessionDurationMs; doReturn(startTimeMs).when(mClock).elapsedRealtime(); @@ -1996,11 +1889,157 @@ // durationTimeMs. ApfFilter.Clock clock = mock(ApfFilter.Clock.class); doReturn(startTimeMs).when(clock).elapsedRealtime(); - final TestAndroidPacketFilter apfFilter2 = new TestApfFilter(mContext, config, - ipClientCallback, mNetworkQuirkMetrics, mDependencies, clock); + 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 ApfFilter.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 ApfFilter.Clock()); + } + + TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, + MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, + NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, + ApfFilter.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, ApfFilter.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 00982c7..71f7ebf 100644 --- a/tests/unit/src/android/net/ip/IpClientTest.java +++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -16,11 +16,17 @@ package android.net.ip; +import static android.net.apf.BaseApfGenerator.APF_VERSION_6; import static android.net.ip.IpClientLinkObserver.CONFIG_SOCKET_RECV_BUFSIZE; import static android.net.ip.IpClientLinkObserver.SOCKET_RECV_BUFSIZE; +import static android.system.OsConstants.AF_UNSPEC; +import static android.system.OsConstants.ARPHRD_ETHER; +import static android.system.OsConstants.IFA_F_PERMANENT; +import static android.system.OsConstants.IFA_F_TENTATIVE; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK; import static com.android.net.module.util.netlink.NetlinkConstants.RTPROT_KERNEL; import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELROUTE; import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWADDR; @@ -44,9 +50,11 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -67,6 +75,7 @@ import android.net.MacAddress; import android.net.NetworkStackIpMemoryStore; import android.net.RouteInfo; +import android.net.apf.AndroidPacketFilter; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.ip.IpClientLinkObserver.IpClientNetlinkMonitor; @@ -87,8 +96,10 @@ import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.netlink.NduseroptMessage; import com.android.net.module.util.netlink.RtNetlinkAddressMessage; +import com.android.net.module.util.netlink.RtNetlinkLinkMessage; import com.android.net.module.util.netlink.RtNetlinkRouteMessage; import com.android.net.module.util.netlink.StructIfaddrMsg; +import com.android.net.module.util.netlink.StructIfinfoMsg; import com.android.net.module.util.netlink.StructNdOptRdnss; import com.android.net.module.util.netlink.StructNlMsgHdr; import com.android.net.module.util.netlink.StructRtMsg; @@ -105,6 +116,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -135,6 +147,8 @@ private static final String INVALID = "INVALID"; private static final String TEST_IFNAME = "test_wlan0"; private static final int TEST_IFINDEX = 1001; + private static final String TEST_CLAT_IFNAME = "v4-" + TEST_IFNAME; + private static final int TEST_CLAT_IFINDEX = 1002; // See RFC 7042#section-2.1.2 for EUI-48 documentation values. private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01"); private static final int TEST_TIMEOUT_MS = 30_000; @@ -176,6 +190,7 @@ @Mock private FileDescriptor mFd; @Mock private PrintWriter mWriter; @Mock private IpClientNetlinkMonitor mNetlinkMonitor; + @Mock private AndroidPacketFilter mApfFilter; private InterfaceParams mIfParams; private INetlinkMessageProcessor mNetlinkMessageProcessor; @@ -296,6 +311,21 @@ (byte) 0 /* icmp_code */, option, null /* srcaddr */); } + private static RtNetlinkLinkMessage buildRtmLinkMessage(short type, int ifindex, + String ifaceName) { + final StructNlMsgHdr nlmsghdr = + makeNetlinkMessageHeader(type, (short) (NLM_F_REQUEST | NLM_F_ACK)); + final StructIfinfoMsg ifInfoMsg = + new StructIfinfoMsg( + (short) AF_UNSPEC, + ARPHRD_ETHER, + ifindex, + 0 /* flags */, + 0xffffffffL /* change */); + + return new RtNetlinkLinkMessage(nlmsghdr, 0 /* mtu */, ifInfoMsg, TEST_MAC, ifaceName); + } + private void onInterfaceAddressUpdated(final LinkAddress la, int flags) { final RtNetlinkAddressMessage msg = buildRtmAddressMessage(RTM_NEWADDR, la, TEST_IFINDEX, flags); @@ -317,6 +347,12 @@ mNetlinkMessageProcessor.processNetlinkMessage(msg, TEST_UNUSED_REAL_TIME /* whenMs */); } + private void onInterfaceAdded(int ifaceIndex, String ifaceName) { + final RtNetlinkLinkMessage msg = buildRtmLinkMessage(RTM_NEWLINK, ifaceIndex, ifaceName); + mNetlinkMessageProcessor.processNetlinkMessage(msg, TEST_UNUSED_REAL_TIME /* whenMs */); + } + + @Test public void testNullInterfaceNameMostDefinitelyThrows() throws Exception { setTestInterfaceParams(null); @@ -806,16 +842,21 @@ conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips())); if (isApfSupported) { config.withApfCapabilities(new ApfCapabilities(4 /* version */, - 4096 /* maxProgramSize */, 4 /* format */)); + 4096 /* maxProgramSize */, ARPHRD_ETHER)); } ipc.startProvisioning(config.build()); final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass( ApfConfiguration.class); - verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( - any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); + if (isApfSupported) { + verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter( + any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); + } else { + verify(mDependencies, never()).maybeCreateApfFilter( + any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); + } - return configCaptor.getValue(); + return isApfSupported ? configCaptor.getValue() : null; } @Test @IgnoreAfter(Build.VERSION_CODES.R) @@ -872,11 +913,10 @@ final IpClient ipc = makeIpClient(TEST_IFNAME); final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc, false /* isApfSupported */); - assertNull(config.apfCapabilities); - clearInvocations(mDependencies); + assertNull(config); ipc.updateApfCapabilities(new ApfCapabilities(4 /* version */, 4096 /* maxProgramSize */, - 4 /* format */)); + ARPHRD_ETHER)); HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass( @@ -885,10 +925,8 @@ any(), configCaptor.capture(), any(), any(), any(), anyBoolean()); final ApfConfiguration actual = configCaptor.getValue(); assertNotNull(actual); - int expectedApfVersion = SdkLevel.isAtLeastS() ? 4 : 3; - assertEquals(expectedApfVersion, actual.apfCapabilities.apfVersionSupported); - assertEquals(4096, actual.apfCapabilities.maximumApfProgramSize); - assertEquals(4, actual.apfCapabilities.apfPacketFormat); + assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, actual.apfVersionSupported); + assertEquals(4096, actual.apfRamSize); verifyShutdown(ipc); } @@ -897,8 +935,9 @@ public void testDumpApfFilter_withNoException() throws Exception { final IpClient ipc = makeIpClient(TEST_IFNAME); final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc, - false /* isApfSupported */); - assertNull(config.apfCapabilities); + true /* isApfSupported */); + assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, config.apfVersionSupported); + assertEquals(4096, config.apfRamSize); clearInvocations(mDependencies); ipc.dump(mFd, mWriter, null /* args */); verifyShutdown(ipc); @@ -909,11 +948,12 @@ final IpClient ipc = makeIpClient(TEST_IFNAME); final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc, true /* isApfSupported */); - assertNotNull(config.apfCapabilities); + assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, config.apfVersionSupported); + assertEquals(4096, config.apfRamSize); clearInvocations(mDependencies); final ApfCapabilities newApfCapabilities = new ApfCapabilities(4 /* version */, - 8192 /* maxProgramSize */, 4 /* format */); + 8192 /* maxProgramSize */, ARPHRD_ETHER); ipc.updateApfCapabilities(newApfCapabilities); HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(), @@ -926,7 +966,8 @@ final IpClient ipc = makeIpClient(TEST_IFNAME); final ApfConfiguration config = verifyApfFilterCreatedOnStart(ipc, true /* isApfSupported */); - assertNotNull(config.apfCapabilities); + assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, config.apfVersionSupported); + assertEquals(4096, config.apfRamSize); clearInvocations(mDependencies); ipc.updateApfCapabilities(null /* apfCapabilities */); @@ -936,6 +977,105 @@ verifyShutdown(ipc); } + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void testVendorNdOffloadDisabledWhenApfV6Supported() throws Exception { + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) + .thenReturn(mApfFilter); + when(mApfFilter.supportNdOffload()).thenReturn(true); + final IpClient ipc = makeIpClient(TEST_IFNAME); + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .withoutIpReachabilityMonitor() + .withApfCapabilities(new ApfCapabilities(APF_VERSION_6, + 4096 /* maxProgramSize */, ARPHRD_ETHER)) + .build(); + ipc.startProvisioning(config); + final InOrder inOrder = inOrder(mCb); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(false); + + // update clat + onInterfaceAdded(TEST_CLAT_IFINDEX, TEST_CLAT_IFNAME); + verifyShutdown(ipc); + inOrder.verify(mCb, never()).setNeighborDiscoveryOffload(anyBoolean()); + clearInvocations(mApfFilter); + clearInvocations(mCb); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void testVendorNdOffloadEnabledWhenApfV6NotSupported() throws Exception { + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) + .thenReturn(mApfFilter); + when(mApfFilter.supportNdOffload()).thenReturn(false); + final IpClient ipc = makeIpClient(TEST_IFNAME); + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .withoutIpReachabilityMonitor() + .withApfCapabilities(new ApfCapabilities(APF_VERSION_6, + 4096 /* maxProgramSize */, ARPHRD_ETHER)) + .build(); + ipc.startProvisioning(config); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true); + + // update clat + onInterfaceAdded(TEST_CLAT_IFINDEX, TEST_CLAT_IFNAME); + verifyShutdown(ipc); + verify(mCb, times(1)).setNeighborDiscoveryOffload(true); + clearInvocations(mApfFilter); + clearInvocations(mCb); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void testVendorNdOffloadDisabledWhenApfCapabilitiesUpdated() throws Exception { + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) + .thenReturn(mApfFilter); + when(mApfFilter.supportNdOffload()).thenReturn(true); + final IpClient ipc = makeIpClient(TEST_IFNAME); + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .withoutIpReachabilityMonitor() + .build(); + ipc.startProvisioning(config); + ipc.updateApfCapabilities( + new ApfCapabilities(APF_VERSION_6, 4096 /* maxProgramSize */, ARPHRD_ETHER)); + HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); + final InOrder inOrder = inOrder(mCb); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(false); + verifyShutdown(ipc); + inOrder.verify(mCb, never()).setNeighborDiscoveryOffload(anyBoolean()); + clearInvocations(mApfFilter); + clearInvocations(mCb); + } + + @Test + public void testLinkPropertiesUpdate_callSetLinkPropertiesOnApfFilter() throws Exception { + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) + .thenReturn(mApfFilter); + final IpClient ipc = makeIpClient(TEST_IFNAME); + verifyApfFilterCreatedOnStart(ipc, true /* isApfSupported */); + onInterfaceAddressUpdated( + new LinkAddress(TEST_GLOBAL_ADDRESS, IFA_F_TENTATIVE, RT_SCOPE_UNIVERSE), + IFA_F_TENTATIVE); + // mApfFilter.setLinkProperties() is called both in IpClient#handleLinkPropertiesUpdate() + // and IpClient#setLinkProperties(). + verify(mApfFilter, timeout(TEST_TIMEOUT_MS).times(2)).setLinkProperties(any()); + // LinkAddress flag change will trigger mApfFilter.setLinkProperties() + onInterfaceAddressUpdated( + new LinkAddress(TEST_GLOBAL_ADDRESS, IFA_F_PERMANENT, RT_SCOPE_UNIVERSE), + IFA_F_PERMANENT); + // mApfFilter.setLinkProperties() is called only in IpClient#handleLinkPropertiesUpdate(). + // IpClient#setLinkProperties() is not called because Objects.equals(newLp, + // mLinkProperties) returns true and IpClient#handleLinkPropertiesUpdate() is terminated. + verify(mApfFilter, timeout(TEST_TIMEOUT_MS).times(3)).setLinkProperties(any()); + clearInvocations(mDependencies); + clearInvocations(mApfFilter); + verifyShutdown(ipc); + } + private ScanResultInfo makeScanResultInfo(final String ssid, final String bssid) { final ByteBuffer payload = ByteBuffer.allocate(14 /* oui + type + data */); final byte[] data = new byte[10];
diff --git a/tests/unit/src/android/net/util/RawSocketUtilsTest.kt b/tests/unit/src/android/net/util/RawSocketUtilsTest.kt new file mode 100644 index 0000000..45bee54 --- /dev/null +++ b/tests/unit/src/android/net/util/RawSocketUtilsTest.kt
@@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 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.util + +import android.content.Context +import android.net.TetheringManager +import android.system.Os +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.net.module.util.HexDump +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import java.io.FileDescriptor +import java.net.NetworkInterface +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.framework +import org.mockito.Mockito.`when` +import org.mockito.MockitoSession +import org.mockito.quality.Strictness + +@RunWith(DevSdkIgnoreRunner::class) +class RawSocketUtilsTest { + @get:Rule + val ignoreRule = DevSdkIgnoreRule() + companion object { + private const val TEST_IFINDEX = 123 + private const val TEST_IFACENAME = "wlan0" + private const val TEST_SRC_MAC = "FFFFFFFFFFFF" + private const val TEST_DST_MAC = "1234567890AB" + private const val TEST_INVALID_PACKET_IN_HEX = "DEADBEEF" + private const val TEST_PACKET_TYPE_IN_HEX = "88A4" + private const val TEST_VALID_PACKET_IN_HEX = + TEST_DST_MAC + TEST_SRC_MAC + TEST_PACKET_TYPE_IN_HEX + } + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var mockTetheringManager: TetheringManager + @Mock + private lateinit var mockNetworkInterface: NetworkInterface + + // For mocking static methods. + private lateinit var mockitoSession: MockitoSession + + @Before + fun setup() { + mockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(Os::class.java) + .mockStatic(NetworkInterface::class.java) + .mockStatic(SocketUtils::class.java) + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking() + doReturn(mockTetheringManager).`when`(mockContext) + .getSystemService(eq(TetheringManager::class.java)) + `when`(NetworkInterface.getByName(any())).thenReturn(mockNetworkInterface) + doReturn(TEST_IFINDEX).`when`(mockNetworkInterface).index + } + + @After + fun teardown() { + mockitoSession.finishMocking() + // Clear mocks to prevent from stubs holding instances and cause memory leaks. + framework().clearInlineMocks() + } + + @Test + fun sendRawPacketDownStream_invalidTetheredInterface() { + doAnswer { + val callback = it.arguments[1] as TetheringManager.TetheringEventCallback + callback.onTetheredInterfacesChanged(listOf("eth0")) + }.`when`(mockTetheringManager).registerTetheringEventCallback(any(), any()) + assertFailsWith<SecurityException> { + RawSocketUtils.sendRawPacketDownStream( + mockContext, + TEST_IFACENAME, + TEST_INVALID_PACKET_IN_HEX + ) + } + } + + @Test + fun sendRawPacketDownStream_invalidPacket() { + doAnswer { + val callback = it.arguments[1] as TetheringManager.TetheringEventCallback + callback.onTetheredInterfacesChanged(listOf(TEST_IFACENAME)) + }.`when`(mockTetheringManager).registerTetheringEventCallback(any(), any()) + + assertFailsWith<ArrayIndexOutOfBoundsException> { + RawSocketUtils.sendRawPacketDownStream( + mockContext, + TEST_IFACENAME, + TEST_INVALID_PACKET_IN_HEX + ) + } + } + + @Test + fun sendRawPacketDownStream_validPacket() { + doAnswer { + val callback = it.arguments[1] as TetheringManager.TetheringEventCallback + callback.onTetheredInterfacesChanged(listOf(TEST_IFACENAME)) + }.`when`(mockTetheringManager).registerTetheringEventCallback(any(), any()) + + RawSocketUtils.sendRawPacketDownStream( + mockContext, + TEST_IFACENAME, + TEST_VALID_PACKET_IN_HEX + ) + + // Verify interactions with mocked static methods. + val fileDescriptorCaptor = ArgumentCaptor.forClass(FileDescriptor::class.java) + val packetDataCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + val packetDataLengthCaptor = ArgumentCaptor.forClass(Int::class.java) + ExtendedMockito.verify { + Os.sendto( + fileDescriptorCaptor.capture(), + packetDataCaptor.capture(), + eq(0), + packetDataLengthCaptor.capture(), + eq(0), + any() + ) + } + assertEquals(TEST_VALID_PACKET_IN_HEX, HexDump.toHexString(packetDataCaptor.value)) + assertEquals(TEST_VALID_PACKET_IN_HEX.length / 2, packetDataLengthCaptor.value) + // TODO: Verify ifindex and packetType once the members of PacketSocketAddress + // can be accessed. + ExtendedMockito.verify { SocketUtils.closeSocket(eq(fileDescriptorCaptor.value)) } + } +}
diff --git a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java index 8dc3d92..69464cf 100644 --- a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java +++ b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
@@ -130,7 +130,7 @@ CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST); verifyCounterName(Counter.DROPPED_802_3_FRAME, CounterName.CN_DROPPED_802_3_FRAME); verifyCounterName(Counter.DROPPED_ETHERTYPE_NOT_ALLOWED, - CounterName.CN_DROPPED_ETHERTYPE_DENYLISTED); + CounterName.CN_DROPPED_ETHERTYPE_NOT_ALLOWED); verifyCounterName(Counter.DROPPED_ARP_REPLY_SPA_NO_HOST, CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST); verifyCounterName(Counter.DROPPED_IPV4_KEEPALIVE_ACK, @@ -143,5 +143,29 @@ verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST, CounterName.CN_UNKNOWN); 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, + CounterName.CN_PASSED_ARP_BROADCAST_REPLY); + verifyCounterName(Counter.PASSED_ARP_REQUEST, CounterName.CN_PASSED_ARP_REQUEST); + verifyCounterName(Counter.PASSED_IPV4_FROM_DHCPV4_SERVER, + CounterName.CN_PASSED_IPV4_FROM_DHCPV4_SERVER); + verifyCounterName(Counter.PASSED_IPV6_NS_DAD, CounterName.CN_PASSED_IPV6_NS_DAD); + verifyCounterName(Counter.PASSED_IPV6_NS_NO_ADDRESS, + CounterName.CN_PASSED_IPV6_NS_NO_ADDRESS); + verifyCounterName(Counter.PASSED_IPV6_NS_NO_SLLA_OPTION, + CounterName.CN_PASSED_IPV6_NS_NO_SLLA_OPTION); + verifyCounterName(Counter.PASSED_IPV6_NS_TENTATIVE, + CounterName.CN_PASSED_IPV6_NS_TENTATIVE); + verifyCounterName(Counter.PASSED_MLD, CounterName.CN_PASSED_MLD); + verifyCounterName(Counter.DROPPED_IPV4_NON_DHCP4, CounterName.CN_DROPPED_IPV4_NON_DHCP4); + verifyCounterName(Counter.DROPPED_IPV6_NS_INVALID, CounterName.CN_DROPPED_IPV6_NS_INVALID); + verifyCounterName(Counter.DROPPED_IPV6_NS_OTHER_HOST, + CounterName.CN_DROPPED_IPV6_NS_OTHER_HOST); + verifyCounterName(Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD, + CounterName.CN_DROPPED_IPV6_NS_REPLIED_NON_DAD); + verifyCounterName(Counter.DROPPED_ARP_REQUEST_ANYHOST, + CounterName.CN_DROPPED_ARP_REQUEST_ANYHOST); + verifyCounterName(Counter.DROPPED_ARP_REQUEST_REPLIED, + CounterName.CN_DROPPED_ARP_REQUEST_REPLIED); + verifyCounterName(Counter.DROPPED_ARP_V6_ONLY, CounterName.CN_DROPPED_ARP_V6_ONLY); } }
diff --git a/tests/unit/src/com/android/server/connectivity/DdrTrackerTest.java b/tests/unit/src/com/android/server/connectivity/DdrTrackerTest.java new file mode 100644 index 0000000..dcc4291 --- /dev/null +++ b/tests/unit/src/com/android/server/connectivity/DdrTrackerTest.java
@@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 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 com.android.server.connectivity; + +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.NonNull; +import android.net.shared.PrivateDnsConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockitoAnnotations; + +import java.net.InetAddress; + +@RunWith(JUnit4.class) +public final class DdrTrackerTest { + private static final int OFF_MODE = PRIVATE_DNS_MODE_OFF; + private static final int OPPORTUNISTIC_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC; + private static final int STRICT_MODE = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + + private DdrTracker mDdrTracker; + + private static class PrivateDnsConfigBuilder { + private int mMode = OFF_MODE; + private String mHostname = null; + private final InetAddress[] mIps = null; + private final String mDohName = null; + private final InetAddress[] mDohIps = null; + private final String mDohPath = null; + private final int mDohPort = -1; + + PrivateDnsConfigBuilder setMode(int mode) { + mMode = mode; + return this; + } + PrivateDnsConfigBuilder setHostname(String value) { + mHostname = value; + return this; + } + PrivateDnsConfig build() { + return new PrivateDnsConfig(mMode, mHostname, mIps, mDohName, mDohIps, mDohPath, + mDohPort); + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mDdrTracker = new DdrTracker(); + } + + private void testNotifyPrivateDnsSettingsChangedHelper(int mode, @NonNull String dnsProvider) + throws Exception { + final PrivateDnsConfig cfg = + new PrivateDnsConfigBuilder().setMode(mode).setHostname(dnsProvider).build(); + + assertTrue(mDdrTracker.notifyPrivateDnsSettingsChanged(cfg)); + assertEquals(mode, mDdrTracker.getPrivateDnsMode()); + assertEquals(dnsProvider, mDdrTracker.getStrictModeHostname()); + assertFalse(mDdrTracker.notifyPrivateDnsSettingsChanged(cfg)); + } + + @Test + public void testNotifyPrivateDnsSettingsChanged() throws Exception { + // Tests the initial private DNS setting in DdrTracker. + assertEquals(OFF_MODE, mDdrTracker.getPrivateDnsMode()); + + assertEquals("", mDdrTracker.getStrictModeHostname()); + assertFalse(mDdrTracker.notifyPrivateDnsSettingsChanged(new PrivateDnsConfigBuilder() + .setMode(OFF_MODE).build())); + + testNotifyPrivateDnsSettingsChangedHelper(OPPORTUNISTIC_MODE, ""); + testNotifyPrivateDnsSettingsChangedHelper(STRICT_MODE, "example1.com"); + testNotifyPrivateDnsSettingsChangedHelper(STRICT_MODE, "example2.com"); + testNotifyPrivateDnsSettingsChangedHelper(OFF_MODE, ""); + } +}