summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-01-10 19:01:43 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-01-10 19:01:43 +0000
commit2e73039336ca1beacf174e85e84ccad8ec8b7e56 (patch)
tree3813c48b21baee7054a7ddd37fd4d5c44119bd31
parent9c786f8049cb97792c3bbd7b05fac98b600cb496 (diff)
parent8a5811fa39b7fe3b4193ba7619e499418eed7185 (diff)
downloadNetworkStack-aml_tz5_341510010.tar.gz
Snap for 11296156 from 8a5811fa39b7fe3b4193ba7619e499418eed7185 to mainline-tzdata5-releaseaml_tz5_341510070aml_tz5_341510050aml_tz5_341510010aml_tz5_341510010
Change-Id: Iaece0b368116d208ed5f04f43b18ed3c068f889c
-rw-r--r--Android.bp126
-rw-r--r--common/networkstackclient/Android.bp6
-rw-r--r--src/android/net/apf/ApfFilter.java35
-rw-r--r--src/android/net/apf/ApfGenerator.java1148
-rw-r--r--src/android/net/apf/DnsUtils.java2
-rw-r--r--src/android/net/apf/LegacyApfFilter.java34
-rw-r--r--src/android/net/dhcp6/Dhcp6Client.java92
-rw-r--r--src/android/net/dhcp6/Dhcp6Packet.java105
-rw-r--r--src/android/net/ip/IpClient.java200
-rw-r--r--src/android/net/ip/IpReachabilityMonitor.java16
-rwxr-xr-xsrc/com/android/networkstack/util/NetworkStackUtils.java30
-rwxr-xr-xsrc/com/android/server/connectivity/NetworkMonitor.java403
-rw-r--r--tests/integration/Android.bp25
-rw-r--r--tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java294
-rw-r--r--tests/unit/Android.bp25
-rw-r--r--tests/unit/src/android/net/apf/ApfTest.java37
-rw-r--r--tests/unit/src/android/net/apf/ApfV5Test.kt312
-rw-r--r--tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt164
-rw-r--r--tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt3
-rw-r--r--tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java411
20 files changed, 2487 insertions, 981 deletions
diff --git a/Android.bp b/Android.bp
index 4461c267..a7f57ae1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,6 +58,7 @@ java_defaults {
name: "NetworkStackNextEnableDefaults",
enabled: true,
}
+
// This is a placeholder comment to avoid merge conflicts
// as the above target may have different "enabled" values
// depending on the branch
@@ -72,7 +73,7 @@ java_defaults {
"framework-connectivity-t",
"framework-statsd",
"framework-wifi",
- ]
+ ],
}
// Common defaults for NetworkStack integration tests, root tests and coverage tests
@@ -85,7 +86,7 @@ java_defaults {
java_defaults {
name: "NetworkStackReleaseApiLevel",
- defaults:["NetworkStackReleaseTargetSdk"],
+ defaults: ["NetworkStackReleaseTargetSdk"],
sdk_version: module_34_version,
libs: [
"framework-configinfrastructure",
@@ -93,7 +94,7 @@ java_defaults {
"framework-connectivity-t",
"framework-statsd",
"framework-wifi",
- ]
+ ],
}
// Libraries for the API shims
@@ -103,12 +104,12 @@ java_defaults {
"androidx.annotation_annotation",
"networkstack-aidl-latest",
],
- static_libs : [
- "modules-utils-build_system"
+ static_libs: [
+ "modules-utils-build_system",
],
apex_available: [
"com.android.tethering",
- "//apex_available:platform", // For InProcessNetworkStack
+ "//apex_available:platform", // For InProcessNetworkStack
],
min_sdk_version: "30",
}
@@ -124,6 +125,9 @@ java_library {
srcs: ["apishim/common/**/*.java"],
sdk_version: "system_current",
visibility: ["//visibility:private"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding
@@ -137,6 +141,9 @@ java_library {
],
sdk_version: "system_29",
visibility: ["//visibility:private"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -153,6 +160,7 @@ java_library {
visibility: ["//visibility:private"],
lint: {
strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
},
}
@@ -176,6 +184,9 @@ java_library {
],
sdk_version: "module_31",
visibility: ["//visibility:private"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -196,6 +207,9 @@ java_library {
],
sdk_version: "module_33",
visibility: ["//visibility:private"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -217,6 +231,9 @@ java_library {
],
sdk_version: module_34_version,
visibility: ["//visibility:private"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Shims for APIs being added to the current development version of Android. These APIs are not
@@ -228,7 +245,10 @@ java_library {
// are part of the stable shims and scanned when generating jarjar rules.
java_library {
name: "NetworkStackApi35Shims",
- defaults: ["NetworkStackShimsDefaults", "ConnectivityNextEnableDefaults"],
+ defaults: [
+ "NetworkStackShimsDefaults",
+ "ConnectivityNextEnableDefaults",
+ ],
srcs: [
"apishim/35/**/*.java",
],
@@ -243,10 +263,13 @@ java_library {
"framework-connectivity",
"framework-connectivity-t.stubs.module_lib",
"framework-tethering",
- "android.net.ipsec.ike.stubs.module_lib"
+ "android.net.ipsec.ike.stubs.module_lib",
],
sdk_version: "module_current",
visibility: ["//visibility:private"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// API current uses the API current shims directly.
@@ -274,6 +297,9 @@ java_library {
"//packages/modules/Connectivity/service-t",
"//packages/modules/Connectivity/tests:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// API stable uses jarjar to rename the latest stable apishim package from
@@ -281,7 +307,10 @@ java_library {
// the networkstack code.
java_library {
name: "NetworkStackApiStableShims",
- defaults: ["NetworkStackShimsDefaults", "NetworkStackReleaseApiLevel"],
+ defaults: [
+ "NetworkStackShimsDefaults",
+ "NetworkStackReleaseApiLevel",
+ ],
static_libs: [
"NetworkStackShimsCommon",
"NetworkStackApi29Shims",
@@ -297,6 +326,9 @@ java_library {
"//packages/modules/Connectivity/service-t",
"//packages/modules/Connectivity/tests:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Common defaults for android libraries containing network stack code, used to compile variants of
@@ -336,7 +368,7 @@ android_library {
],
srcs: [
"src/**/*.java",
- ":statslog-networkstack-java-gen-current"
+ ":statslog-networkstack-java-gen-current",
],
static_libs: [
"NetworkStackApiCurrentShims",
@@ -348,12 +380,18 @@ android_library {
"//packages/modules/NetworkStack/tests/unit",
"//packages/modules/NetworkStack/tests/integration",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
+ },
}
android_library {
name: "NetworkStackApiStableLib",
- defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"],
+ defaults: [
+ "NetworkStackReleaseApiLevel",
+ "NetworkStackAndroidLibraryDefaults",
+ ],
srcs: [
"src/**/*.java",
":statslog-networkstack-java-gen-stable",
@@ -370,7 +408,10 @@ android_library {
"//packages/modules/NetworkStack/tests/unit",
"//packages/modules/NetworkStack/tests/integration",
],
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -396,6 +437,9 @@ java_library {
"//packages/modules/Connectivity/Tethering/tests/integration",
"//packages/modules/Connectivity/tests/cts/net",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_genrule {
@@ -455,12 +499,18 @@ android_app {
certificate: "platform",
manifest: "AndroidManifest_InProcess.xml",
// InProcessNetworkStack is a replacement for NetworkStack
- overrides: ["NetworkStack", "NetworkStackNext"],
+ overrides: [
+ "NetworkStack",
+ "NetworkStackNext",
+ ],
// The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces
// the default CaptivePortalLogin.
required: [
"PlatformCaptivePortalLogin",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top
@@ -472,7 +522,10 @@ android_library {
"ConnectivityNextEnableDefaults",
],
static_libs: ["NetworkStackApiCurrentLib"],
- manifest: "AndroidManifest.xml"
+ manifest: "AndroidManifest.xml",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// NetworkStack build targeting the current API release, for testing on in-development SDK
@@ -490,13 +543,19 @@ android_app {
"privapp_whitelist_com.android.networkstack",
],
updatable: true,
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Updatable network stack for finalized API
android_app {
name: "NetworkStack",
- defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
+ defaults: [
+ "NetworkStackAppDefaults",
+ "NetworkStackReleaseApiLevel",
+ ],
static_libs: ["NetworkStackApiStableLib"],
certificate: "networkstack",
manifest: "AndroidManifest.xml",
@@ -504,13 +563,16 @@ android_app {
"privapp_whitelist_com.android.networkstack",
],
updatable: true,
- lint: { strict_updatability_linting: true },
+ lint: {
+ strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
+ },
}
cc_library_shared {
name: "libnetworkstackutilsjni",
srcs: [
- "jni/network_stack_utils_jni.cpp"
+ "jni/network_stack_utils_jni.cpp",
],
header_libs: [
"bpf_headers",
@@ -548,8 +610,8 @@ genrule {
name: "statslog-networkstack-java-gen-current",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
- " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
- " --minApiLevel 30",
+ " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
+ " --minApiLevel 30",
out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"],
}
@@ -557,12 +619,11 @@ genrule {
name: "statslog-networkstack-java-gen-stable",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
- " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
- " --minApiLevel 30 --compileApiLevel 30",
+ " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
+ " --minApiLevel 30 --compileApiLevel 30",
out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"],
}
-
version_code_networkstack_next = "300000000"
version_code_networkstack_test = "999999999"
@@ -570,21 +631,27 @@ genrule {
name: "NetworkStackTestAndroidManifest",
srcs: ["AndroidManifest.xml"],
out: ["TestAndroidManifest.xml"],
- cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\""
- + version_code_networkstack_test
- + "\"/' $(in) > $(out)",
+ cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\"" +
+ version_code_networkstack_test +
+ "\"/' $(in) > $(out)",
visibility: ["//visibility:private"],
}
android_app {
name: "TestNetworkStack",
- defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
+ defaults: [
+ "NetworkStackAppDefaults",
+ "NetworkStackReleaseApiLevel",
+ ],
static_libs: ["NetworkStackApiStableLib"],
certificate: "networkstack",
manifest: ":NetworkStackTestAndroidManifest",
required: [
"privapp_whitelist_com.android.networkstack",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// When adding or modifying protos, the jarjar rules and possibly proguard rules need
@@ -601,4 +668,7 @@ java_library_static {
"networkstackprotos",
],
defaults: ["NetworkStackReleaseApiLevel"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 07fd52e1..060f0da7 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -221,6 +221,9 @@ java_library {
"com.android.tethering",
"com.android.wifi",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -265,4 +268,7 @@ java_library {
"com.android.tethering",
"com.android.wifi",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 2ef9c08e..ee2990b8 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -1148,7 +1148,7 @@ public class ApfFilter implements AndroidPacketFilter {
// Generate code to match the packet bytes.
if (section.type == PacketSection.Type.MATCH) {
gen.addLoadImmediate(Register.R0, section.start);
- gen.addJumpIfBytesNotEqual(Register.R0,
+ gen.addJumpIfBytesAtR0NotEqual(
Arrays.copyOfRange(mPacket.array(), section.start,
section.start + section.length),
nextFilterLabel);
@@ -1283,7 +1283,7 @@ public class ApfFilter implements AndroidPacketFilter {
final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
// A NAT-T keepalive packet contains 1 byte payload with the value 0xff
// Check payload length is 1
@@ -1298,11 +1298,11 @@ public class ApfFilter implements AndroidPacketFilter {
// Check that the ports match
gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
gen.addAdd(ETH_HEADER_LEN);
- gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
// Payload offset = R0 + UDP header length
gen.addAdd(UDP_HEADER_LEN);
- gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE);
gen.addJump(mCountAndDropLabel);
@@ -1398,7 +1398,7 @@ public class ApfFilter implements AndroidPacketFilter {
final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
// Skip to the next filter if it's not zero-sized :
// TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
@@ -1420,7 +1420,7 @@ public class ApfFilter implements AndroidPacketFilter {
gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN);
gen.addAddR1();
- gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
gen.addJump(mCountAndDropLabel);
@@ -1522,7 +1522,7 @@ public class ApfFilter implements AndroidPacketFilter {
// Drop if not ARP IPv4.
gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
maybeSetupCounter(gen, Counter.DROPPED_ARP_NON_IPV4);
- gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndDropLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndDropLabel);
// Drop if unknown ARP opcode.
gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
@@ -1538,7 +1538,7 @@ public class ApfFilter implements AndroidPacketFilter {
// Pass if non-broadcast reply.
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
- gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
// Either a request, or a broadcast reply.
gen.defineLabel(checkTargetIPv4);
@@ -1552,7 +1552,7 @@ public class ApfFilter implements AndroidPacketFilter {
// and broadcast replies with a different target IPv4 address.
gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
- gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
}
maybeSetupCounter(gen, Counter.PASSED_ARP);
@@ -1600,7 +1600,7 @@ public class ApfFilter implements AndroidPacketFilter {
gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
// NOTE: Relies on R1 containing IPv4 header offset.
gen.addAddR1();
- gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
+ gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
maybeSetupCounter(gen, Counter.PASSED_DHCP);
gen.addJump(mCountAndPassLabel);
@@ -1639,7 +1639,7 @@ public class ApfFilter implements AndroidPacketFilter {
// TODO: can we invert this condition to fall through to the common pass case below?
maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
gen.addJump(mCountAndDropLabel);
}
@@ -1763,8 +1763,7 @@ public class ApfFilter implements AndroidPacketFilter {
// TODO: Drop only if they don't contain the address of on-link neighbours.
final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix,
- skipUnsolicitedMulticastNALabel);
+ gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
gen.addJump(mCountAndDropLabel);
@@ -1821,8 +1820,7 @@ public class ApfFilter implements AndroidPacketFilter {
// Check it's L2 mDNS multicast address.
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
- skipMdnsv4Filter);
+ gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter);
// Checks it's IPv4.
gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
@@ -1845,8 +1843,7 @@ public class ApfFilter implements AndroidPacketFilter {
// Checks it's L2 mDNS multicast address.
// Relies on R0 containing the ethernet destination mac address offset.
- gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS,
- skipMdnsFilter);
+ gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
// Checks it's IPv6.
gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
@@ -1877,7 +1874,7 @@ public class ApfFilter implements AndroidPacketFilter {
for (int i = 0; i < mMdnsAllowList.size(); ++i) {
final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i;
final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i));
- gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck);
+ gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck);
// QNAME matched
gen.addJump(mDnsAcceptPacket);
// QNAME not matched
@@ -2023,7 +2020,7 @@ public class ApfFilter implements AndroidPacketFilter {
// Drop non-IP non-ARP broadcasts, pass the rest
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
- gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
gen.addJump(mCountAndDropLabel);
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java
index 0c4007bc..6346a02b 100644
--- a/src/android/net/apf/ApfGenerator.java
+++ b/src/android/net/apf/ApfGenerator.java
@@ -16,7 +16,13 @@
package android.net.apf;
+import static android.net.apf.ApfGenerator.Register.R0;
+import static android.net.apf.ApfGenerator.Register.R1;
+
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.HexDump;
import java.util.ArrayList;
import java.util.HashMap;
@@ -26,7 +32,7 @@ import java.util.List;
* APF assembler/generator. A tool for generating an APF program.
*
* Call add*() functions to add instructions to the program, then call
- * {@link generate} to get the APF bytecode for the program.
+ * {@link ApfGenerator#generate} to get the APF bytecode for the program.
*
* @hide
*/
@@ -63,7 +69,12 @@ public class ApfGenerator {
OR(11), // Or, e.g. "or R0,5"
SH(12), // Left shift, e.g, "sh R0, 5" or "sh R0, -5" (shifts right)
LI(13), // Load immediate, e.g. "li R0,5" (immediate encoded as signed value)
- JMP(14), // Jump, e.g. "jmp label"
+ // Jump, e.g. "jmp label"
+ // In APFv6, we use JMP(R=1) to encode the DATA instruction. DATA is executed as a jump.
+ // It tells how many bytes of the program regions are used to store the data and followed
+ // by the actual data bytes.
+ // "e.g. data 5, abcde"
+ JMP(14),
JEQ(15), // Compare equal and branch, e.g. "jeq R0,5,label"
JNE(16), // Compare not equal and branch, e.g. "jne R0,5,label"
JGT(17), // Compare greater than and branch, e.g. "jgt R0,5,label"
@@ -73,12 +84,17 @@ public class ApfGenerator {
EXT(21), // Followed by immediate indicating ExtendedOpcodes.
LDDW(22), // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1"
STDW(23), // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
- WRITE(24), // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5"
- // Copy the data from input packet or APF data region to output buffer. Register bit is
- // used to specify the source of data copy: R=0 means copy from packet, R=1 means copy
- // from APF data region. The source offset is encoded in the first imm and the copy length
- // is encoded in the second imm. "e.g. MEMCOPY(R=0), 5, 5"
- MEMCOPY(25);
+ // Write 1, 2 or 4 bytes immediate to the output buffer and auto-increment the pointer to
+ // write. e.g. "write 5"
+ WRITE(24),
+ // Copy bytes from input packet/APF program/data region to output buffer and
+ // auto-increment the output buffer pointer.
+ // Register bit is used to specify the source of data copy.
+ // R=0 means copy from packet.
+ // R=1 means copy from APF program/data region.
+ // The copy length is stored in (u8)imm2.
+ // e.g. "pktcopy 5, 5" "datacopy 5, 5"
+ PKTDATACOPY(25);
final int value;
@@ -95,22 +111,50 @@ public class ApfGenerator {
NEG(33), // Negate, e.g. "neg R0"
SWAP(34), // Swap, e.g. "swap R0,R1"
MOVE(35), // Move, e.g. "move R0,R1"
- ALLOC(36), // Allocate buffer, "e.g. ALLOC R0"
+ // Allocate writable output buffer.
+ // R=0, use register R0 to store the length. R=1, encode the length in the u16 int imm2.
+ // "e.g. allocate R0"
+ // "e.g. allocate 123"
+ ALLOCATE(36),
// Transmit and deallocate the buffer (transmission can be delayed until the program
// terminates). R=0 means discard the buffer, R=1 means transmit the buffer.
// "e.g. trans"
// "e.g. discard"
TRANSMIT(37),
DISCARD(37),
- EWRITE1(38), // Write 1 byte from register to the output buffer, e.g. "EWRITE1 R0"
- EWRITE2(39), // Write 2 bytes from register to the output buffer, e.g. "EWRITE2 R0"
- EWRITE4(40), // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0"
- // Copy the data from input packet to output buffer. The source offset is encoded as [Rx
- // + second imm]. The copy length is encoded in the third imm. "e.g. EPKTCOPY [R0 + 5], 5"
+ // Write 1, 2 or 4 byte value from register to the output buffer and auto-increment the
+ // output buffer pointer.
+ // e.g. "ewrite1 r0"
+ EWRITE1(38),
+ EWRITE2(39),
+ EWRITE4(40),
+ // Copy bytes from input packet/APF program/data region to output buffer and
+ // auto-increment the output buffer pointer.
+ // The copy src offset is stored in R0.
+ // when R=0, the copy length is stored in (u8)imm2.
+ // when R=1, the copy length is stored in R1.
+ // e.g. "pktcopy r0, 5", "pktcopy r0, r1", "datacopy r0, 5", "datacopy r0, r1"
EPKTCOPY(41),
- // Copy the data from APF data region to output buffer. The source offset is encoded as [Rx
- // + second imm]. The copy length is encoded in the third imm. "e.g. EDATACOPY [R0 + 5], 5"
- EDATACOPY(42);
+ EDATACOPY(42),
+ // Jumps if the UDP payload content (starting at R0) does not contain ont
+ // of the specified QNAME, applying case insensitivity.
+ // R0: Offset to UDP payload content
+ // R=0/1 meanining 'does not match' vs 'matches'
+ // imm1: Opcode
+ // imm2: Label offset
+ // imm3(u8): Question type (PTR/SRV/TXT/A/AAAA)
+ // imm4(bytes): TLV-encoded QNAME list (null-terminated)
+ // e.g.: "jdnsqmatch R0,label,0x0c,\002aa\005local\0\0"
+ JDNSQMATCH(43), // Jumps if the UDP payload content (starting at R0) does not contain one
+ // of the specified NAME in answers/authority/additional records, applying
+ // case insensitivity.
+ // R=0/1 meanining 'does not match' vs 'matches'
+ // R0: Offset to UDP payload content
+ // imm1: Opcode
+ // imm2: Label offset
+ // imm3(bytes): TLV-encoded QNAME list (null-terminated)
+ // e.g.: "jdnsamatch R0,label,0x0c,\002aa\005local\0\0"
+ JDNSAMATCH(44);
final int value;
@@ -129,34 +173,136 @@ public class ApfGenerator {
}
}
- private static class Immediate {
- public final boolean mSigned;
- public final byte mImmSize;
+ private enum IntImmediateType {
+ INDETERMINATE_SIZE_SIGNED,
+ INDETERMINATE_SIZE_UNSIGNED,
+ SIGNED_8,
+ UNSIGNED_8,
+ SIGNED_BE16,
+ UNSIGNED_BE16,
+ SIGNED_BE32,
+ UNSIGNED_BE32;
+ }
+
+ private static class IntImmediate {
+ public final IntImmediateType mImmediateType;
public final int mValue;
- Immediate(int value, boolean signed) {
- this(value, signed, calculateImmSize(value, signed));
+ IntImmediate(int value, IntImmediateType type) {
+ mImmediateType = type;
+ mValue = value;
}
- Immediate(int value, boolean signed, byte size) {
- mValue = value;
- mSigned = signed;
- mImmSize = size;
+ private int calculateIndeterminateSize() {
+ switch (mImmediateType) {
+ case INDETERMINATE_SIZE_SIGNED:
+ return calculateImmSize(mValue, true /* signed */);
+ case INDETERMINATE_SIZE_UNSIGNED:
+ return calculateImmSize(mValue, false /* signed */);
+ default:
+ // For IMM with determinate size, return 0 to allow Math.max() calculation in
+ // caller function.
+ return 0;
+ }
+ }
+
+ private int getEncodingSize(int immFieldSize) {
+ switch (mImmediateType) {
+ case SIGNED_8:
+ case UNSIGNED_8:
+ return 1;
+ case SIGNED_BE16:
+ case UNSIGNED_BE16:
+ return 2;
+ case SIGNED_BE32:
+ case UNSIGNED_BE32:
+ return 4;
+ case INDETERMINATE_SIZE_SIGNED:
+ case INDETERMINATE_SIZE_UNSIGNED: {
+ int minSizeRequired = calculateIndeterminateSize();
+ if (minSizeRequired > immFieldSize) {
+ throw new IllegalStateException(
+ String.format("immFieldSize: %d is too small to encode value %d",
+ immFieldSize, mValue));
+ }
+ return immFieldSize;
+ }
+ }
+ throw new IllegalStateException("UnhandledInvalid IntImmediateType: " + mImmediateType);
+ }
+
+ private int writeValue(byte[] bytecode, Integer writingOffset, int immFieldSize) {
+ return Instruction.writeValue(mValue, bytecode, writingOffset,
+ getEncodingSize(immFieldSize));
+ }
+
+ public static IntImmediate newSigned(int imm) {
+ return new IntImmediate(imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
+ }
+
+ public static IntImmediate newUnsigned(long imm) {
+ // upperBound is 2^32 - 1
+ checkRange("Unsigned IMM", imm, 0 /* lowerBound */,
+ 4294967295L /* upperBound */);
+ return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
+ }
+
+ public static IntImmediate newTwosComplementUnsigned(long imm) {
+ checkRange("Unsigned TwosComplement IMM", imm, Integer.MIN_VALUE,
+ 4294967295L /* upperBound */);
+ return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
+ }
+
+ public static IntImmediate newTwosComplementSigned(long imm) {
+ checkRange("Signed TwosComplement IMM", imm, Integer.MIN_VALUE,
+ 4294967295L /* upperBound */);
+ return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
+ }
+
+ public static IntImmediate newS8(byte imm) {
+ checkRange("S8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE);
+ return new IntImmediate(imm, IntImmediateType.SIGNED_8);
+ }
+
+ public static IntImmediate newU8(int imm) {
+ checkRange("U8 IMM", imm, 0, 255);
+ return new IntImmediate(imm, IntImmediateType.UNSIGNED_8);
+ }
+
+ public static IntImmediate newS16(short imm) {
+ return new IntImmediate(imm, IntImmediateType.SIGNED_BE16);
+ }
+
+ public static IntImmediate newU16(int imm) {
+ checkRange("U16 IMM", imm, 0, 65535);
+ return new IntImmediate(imm, IntImmediateType.UNSIGNED_BE16);
+ }
+
+ public static IntImmediate newS32(int imm) {
+ return new IntImmediate(imm, IntImmediateType.SIGNED_BE32);
+ }
+
+ public static IntImmediate newU32(long imm) {
+ // upperBound is 2^32 - 1
+ checkRange("U32 IMM", imm, 0 /* lowerBound */,
+ 4294967295L /* upperBound */);
+ return new IntImmediate((int) imm, IntImmediateType.UNSIGNED_BE32);
}
@Override
public String toString() {
- return "Immediate{" + "mSigned=" + mSigned + ", mImmSize=" + mImmSize + ", mValue="
- + mValue + '}';
+ return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mValue=" + mValue
+ + '}';
}
}
private class Instruction {
private final byte mOpcode; // A "Opcode" value.
private final byte mRegister; // A "Register" value.
- public final List<Immediate> mImms = new ArrayList<>();
+ public final List<IntImmediate> mIntImms = new ArrayList<>();
// When mOpcode is a jump:
- private byte mTargetLabelSize;
+ private int mTargetLabelSize;
+ private int mLenFieldOverride = -1;
private String mTargetLabel;
// When mOpcode == Opcodes.LABEL:
private String mLabel;
@@ -169,27 +315,81 @@ public class ApfGenerator {
mRegister = (byte) register.value;
}
+ Instruction(ExtendedOpcodes extendedOpcodes, Register register) {
+ this(Opcodes.EXT, register);
+ addUnsigned(extendedOpcodes.value);
+ }
+
+ Instruction(ExtendedOpcodes extendedOpcodes, int slot, Register register)
+ throws IllegalInstructionException {
+ this(Opcodes.EXT, register);
+ if (slot < 0 || slot >= MEMORY_SLOTS) {
+ throw new IllegalInstructionException("illegal memory slot number: " + slot);
+ }
+ addUnsigned(extendedOpcodes.value + slot);
+ }
+
Instruction(Opcodes opcode) {
- this(opcode, Register.R0);
+ this(opcode, R0);
+ }
+
+ Instruction(ExtendedOpcodes extendedOpcodes) {
+ this(extendedOpcodes, R0);
+ }
+
+ Instruction addSigned(int imm) {
+ mIntImms.add(IntImmediate.newSigned(imm));
+ return this;
+ }
+
+ Instruction addUnsigned(int imm) {
+ mIntImms.add(IntImmediate.newUnsigned(imm));
+ return this;
+ }
+
+
+ Instruction addTwosCompSigned(int imm) {
+ mIntImms.add(IntImmediate.newTwosComplementSigned(imm));
+ return this;
+ }
+
+
+ Instruction addTwosCompUnsigned(int imm) {
+ mIntImms.add(IntImmediate.newTwosComplementUnsigned(imm));
+ return this;
}
- void addUnsignedImm(int imm) {
- addImm(new Immediate(imm, false));
+ Instruction addS8(byte imm) {
+ mIntImms.add(IntImmediate.newS8(imm));
+ return this;
}
- void addUnsignedImm(int imm, byte size) {
- addImm(new Immediate(imm, false, size));
+ Instruction addU8(int imm) {
+ mIntImms.add(IntImmediate.newU8(imm));
+ return this;
}
- void addSignedImm(int imm) {
- addImm(new Immediate(imm, true));
+ Instruction addS16(short imm) {
+ mIntImms.add(IntImmediate.newS16(imm));
+ return this;
}
- void addImm(Immediate imm) {
- mImms.add(imm);
+ Instruction addU16(int imm) {
+ mIntImms.add(IntImmediate.newU16(imm));
+ return this;
}
- void setLabel(String label) throws IllegalInstructionException {
+ Instruction addS32(int imm) {
+ mIntImms.add(IntImmediate.newS32(imm));
+ return this;
+ }
+
+ Instruction addU32(long imm) {
+ mIntImms.add(IntImmediate.newU32(imm));
+ return this;
+ }
+
+ Instruction setLabel(String label) throws IllegalInstructionException {
if (mLabels.containsKey(label)) {
throw new IllegalInstructionException("duplicate label " + label);
}
@@ -198,18 +398,23 @@ public class ApfGenerator {
}
mLabel = label;
mLabels.put(label, this);
+ return this;
}
- void setTargetLabel(String label) {
+ Instruction setTargetLabel(String label) {
mTargetLabel = label;
mTargetLabelSize = 4; // May shrink later on in generate().
+ return this;
}
- void setBytesImm(byte[] bytes) {
- if (mOpcode != Opcodes.JNEBS.value) {
- throw new IllegalStateException("adding compare bytes to non-JNEBS instruction");
- }
+ Instruction overrideLenField(int size) {
+ mLenFieldOverride = size;
+ return this;
+ }
+
+ Instruction setBytesImm(byte[] bytes) {
mBytesImm = bytes;
+ return this;
}
/**
@@ -220,11 +425,12 @@ public class ApfGenerator {
return 0;
}
int size = 1;
- byte maxImmSize = getMaxImmSize();
- // For the copy opcode, the last imm is the length field is always 1 byte
- size += mImms.size() * maxImmSize;
+ int indeterminateSize = calculateRequiredIndeterminateSize();
+ for (IntImmediate imm : mIntImms) {
+ size += imm.getEncodingSize(indeterminateSize);
+ }
if (mTargetLabel != null) {
- size += maxImmSize;
+ size += indeterminateSize;
}
if (mBytesImm != null) {
size += mBytesImm.length;
@@ -241,20 +447,34 @@ public class ApfGenerator {
if (mTargetLabel == null) {
return false;
}
- int oldSize = size();
int oldTargetLabelSize = mTargetLabelSize;
mTargetLabelSize = calculateImmSize(calculateTargetLabelOffset(), false);
if (mTargetLabelSize > oldTargetLabelSize) {
throw new IllegalStateException("instruction grew");
}
- return size() < oldSize;
+ return mTargetLabelSize < oldTargetLabelSize;
}
/**
* Assemble value for instruction size field.
*/
- private byte generateImmSizeField() {
- byte immSize = getMaxImmSize();
+ private int generateImmSizeField() {
+ // If we already know the size the length field, just use it
+ switch (mLenFieldOverride) {
+ case -1:
+ break;
+ case 1:
+ return 1;
+ case 2:
+ return 2;
+ case 4:
+ return 3;
+ default:
+ throw new IllegalStateException(
+ "mLenFieldOverride has invalid value: " + mLenFieldOverride);
+ }
+ // Otherwise, calculate
+ int immSize = calculateRequiredIndeterminateSize();
// Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4.
return immSize == 4 ? 3 : immSize;
}
@@ -263,7 +483,7 @@ public class ApfGenerator {
* Assemble first byte of generated instruction.
*/
private byte generateInstructionByte() {
- byte sizeField = generateImmSizeField();
+ int sizeField = generateImmSizeField();
return (byte)((mOpcode << 3) | (sizeField << 1) | mRegister);
}
@@ -276,7 +496,7 @@ public class ApfGenerator {
* be sign extended and the truncation should simply throw away their signed
* upper bits.
*/
- private int writeValue(int value, byte[] bytecode, int writingOffset, byte immSize) {
+ private static int writeValue(int value, byte[] bytecode, int writingOffset, int immSize) {
for (int i = immSize - 1; i >= 0; i--) {
bytecode[writingOffset++] = (byte)((value >> (i * 8)) & 255);
}
@@ -284,7 +504,7 @@ public class ApfGenerator {
}
/**
- * Generate bytecode for this instruction at offset {@link offset}.
+ * Generate bytecode for this instruction at offset {@link Instruction#offset}.
*/
void generate(byte[] bytecode) throws IllegalInstructionException {
if (mOpcode == Opcodes.LABEL.value) {
@@ -292,13 +512,20 @@ public class ApfGenerator {
}
int writingOffset = offset;
bytecode[writingOffset++] = generateInstructionByte();
- byte maxImmSize = getMaxImmSize();
+ int indeterminateSize = calculateRequiredIndeterminateSize();
+ int startOffset = 0;
+ if (mOpcode == Opcodes.EXT.value) {
+ // For extend opcode, always write the actual opcode first.
+ writingOffset = mIntImms.get(startOffset++).writeValue(bytecode, writingOffset,
+ indeterminateSize);
+ }
if (mTargetLabel != null) {
writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset,
- maxImmSize);
+ indeterminateSize);
}
- for (Immediate imm : mImms) {
- writingOffset = writeValue(imm.mValue, bytecode, writingOffset, maxImmSize);
+ for (int i = startOffset; i < mIntImms.size(); ++i) {
+ writingOffset = mIntImms.get(i).writeValue(bytecode, writingOffset,
+ indeterminateSize);
}
if (mBytesImm != null) {
System.arraycopy(mBytesImm, 0, bytecode, writingOffset, mBytesImm.length);
@@ -311,17 +538,15 @@ public class ApfGenerator {
}
/**
- * Calculate the size of either the immediate fields or the target label field, if either is
- * present. Most instructions have either immediates or a target label field, but for the
- * instructions that have both, the size of the target label field must be the same as the
- * size of the immediate fields, because there is only one length field in the instruction
- * byte, hence why this function simply takes the maximum of those sizes, so neither is
- * truncated.
+ * Calculates the maximum indeterminate size of all IMMs in this instruction.
+ * <p>
+ * This method finds the largest size needed to encode any indeterminate-sized IMMs in
+ * the instruction. This size will be stored in the immLen field.
*/
- private byte getMaxImmSize() {
- byte maxSize = mTargetLabelSize;
- for (int i = 0; i < mImms.size(); ++i) {
- maxSize = (byte) Math.max(maxSize, mImms.get(i).mImmSize);
+ private int calculateRequiredIndeterminateSize() {
+ int maxSize = mTargetLabelSize;
+ for (IntImmediate imm : mIntImms) {
+ maxSize = Math.max(maxSize, imm.calculateIndeterminateSize());
}
return maxSize;
}
@@ -401,6 +626,7 @@ public class ApfGenerator {
// This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h
public static final int MIN_APF_VERSION = 2;
public static final int MIN_APF_VERSION_IN_DEV = 5;
+ public static final int APF_VERSION_4 = 4;
private final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>();
@@ -434,11 +660,12 @@ public class ApfGenerator {
}
}
- private void addInstruction(Instruction instruction) {
+ private ApfGenerator append(Instruction instruction) {
if (mGenerated) {
throw new IllegalStateException("Program already generated");
}
mInstructions.add(instruction);
+ return this;
}
/**
@@ -457,53 +684,38 @@ public class ApfGenerator {
* In this case "next_filter" may not have any generated code associated with it.
*/
public ApfGenerator defineLabel(String name) throws IllegalInstructionException {
- Instruction instruction = new Instruction(Opcodes.LABEL);
- instruction.setLabel(name);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.LABEL).setLabel(name));
}
/**
* Add an unconditional jump instruction to the end of the program.
*/
public ApfGenerator addJump(String target) {
- Instruction instruction = new Instruction(Opcodes.JMP);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.JMP).setTargetLabel(target));
}
/**
* Add an instruction to the end of the program to load the byte at offset {@code offset}
* bytes from the beginning of the packet into {@code register}.
*/
- public ApfGenerator addLoad8(Register register, int offset) {
- Instruction instruction = new Instruction(Opcodes.LDB, register);
- instruction.addUnsignedImm(offset);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addLoad8(Register r, int ofs) {
+ return append(new Instruction(Opcodes.LDB, r).addUnsigned(ofs));
}
/**
* Add an instruction to the end of the program to load 16-bits at offset {@code offset}
* bytes from the beginning of the packet into {@code register}.
*/
- public ApfGenerator addLoad16(Register register, int offset) {
- Instruction instruction = new Instruction(Opcodes.LDH, register);
- instruction.addUnsignedImm(offset);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addLoad16(Register r, int ofs) {
+ return append(new Instruction(Opcodes.LDH, r).addUnsigned(ofs));
}
/**
* Add an instruction to the end of the program to load 32-bits at offset {@code offset}
* bytes from the beginning of the packet into {@code register}.
*/
- public ApfGenerator addLoad32(Register register, int offset) {
- Instruction instruction = new Instruction(Opcodes.LDW, register);
- instruction.addUnsignedImm(offset);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addLoad32(Register r, int ofs) {
+ return append(new Instruction(Opcodes.LDW, r).addUnsigned(ofs));
}
/**
@@ -511,11 +723,8 @@ public class ApfGenerator {
* {@code register}. The offset of the loaded byte from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
- public ApfGenerator addLoad8Indexed(Register register, int offset) {
- Instruction instruction = new Instruction(Opcodes.LDBX, register);
- instruction.addUnsignedImm(offset);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addLoad8Indexed(Register r, int ofs) {
+ return append(new Instruction(Opcodes.LDBX, r).addUnsigned(ofs));
}
/**
@@ -523,11 +732,8 @@ public class ApfGenerator {
* {@code register}. The offset of the loaded 16-bits from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
- public ApfGenerator addLoad16Indexed(Register register, int offset) {
- Instruction instruction = new Instruction(Opcodes.LDHX, register);
- instruction.addUnsignedImm(offset);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addLoad16Indexed(Register r, int ofs) {
+ return append(new Instruction(Opcodes.LDHX, r).addUnsigned(ofs));
}
/**
@@ -535,109 +741,81 @@ public class ApfGenerator {
* {@code register}. The offset of the loaded 32-bits from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
- public ApfGenerator addLoad32Indexed(Register register, int offset) {
- Instruction instruction = new Instruction(Opcodes.LDWX, register);
- instruction.addUnsignedImm(offset);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addLoad32Indexed(Register r, int ofs) {
+ return append(new Instruction(Opcodes.LDWX, r).addUnsigned(ofs));
}
/**
* Add an instruction to the end of the program to add {@code value} to register R0.
*/
- public ApfGenerator addAdd(int value) {
- Instruction instruction = new Instruction(Opcodes.ADD);
- instruction.addUnsignedImm(value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addAdd(int val) {
+ return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val));
}
/**
* Add an instruction to the end of the program to multiply register R0 by {@code value}.
*/
- public ApfGenerator addMul(int value) {
- Instruction instruction = new Instruction(Opcodes.MUL);
- instruction.addUnsignedImm(value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addMul(int val) {
+ return append(new Instruction(Opcodes.MUL).addUnsigned(val));
}
/**
* Add an instruction to the end of the program to divide register R0 by {@code value}.
*/
- public ApfGenerator addDiv(int value) {
- Instruction instruction = new Instruction(Opcodes.DIV);
- instruction.addUnsignedImm(value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addDiv(int val) {
+ return append(new Instruction(Opcodes.DIV).addUnsigned(val));
}
/**
* Add an instruction to the end of the program to logically and register R0 with {@code value}.
*/
- public ApfGenerator addAnd(int value) {
- Instruction instruction = new Instruction(Opcodes.AND);
- instruction.addUnsignedImm(value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addAnd(int val) {
+ return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val));
}
/**
* Add an instruction to the end of the program to logically or register R0 with {@code value}.
*/
- public ApfGenerator addOr(int value) {
- Instruction instruction = new Instruction(Opcodes.OR);
- instruction.addUnsignedImm(value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addOr(int val) {
+ return append(new Instruction(Opcodes.OR).addTwosCompUnsigned(val));
}
/**
* Add an instruction to the end of the program to shift left register R0 by {@code value} bits.
*/
- public ApfGenerator addLeftShift(int value) {
- Instruction instruction = new Instruction(Opcodes.SH);
- instruction.addSignedImm(value);
- addInstruction(instruction);
- return this;
+ // TODO: consider whether should change the argument type to byte
+ public ApfGenerator addLeftShift(int val) {
+ return append(new Instruction(Opcodes.SH).addSigned(val));
}
/**
* Add an instruction to the end of the program to shift right register R0 by {@code value}
* bits.
*/
- public ApfGenerator addRightShift(int value) {
- Instruction instruction = new Instruction(Opcodes.SH);
- instruction.addSignedImm(-value);
- addInstruction(instruction);
- return this;
+ // TODO: consider whether should change the argument type to byte
+ public ApfGenerator addRightShift(int val) {
+ return append(new Instruction(Opcodes.SH).addSigned(-val));
}
/**
* Add an instruction to the end of the program to add register R1 to register R0.
*/
public ApfGenerator addAddR1() {
- Instruction instruction = new Instruction(Opcodes.ADD, Register.R1);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.ADD, R1));
}
/**
* Add an instruction to the end of the program to multiply register R0 by register R1.
*/
public ApfGenerator addMulR1() {
- Instruction instruction = new Instruction(Opcodes.MUL, Register.R1);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.MUL, R1));
}
/**
* Add an instruction to the end of the program to divide register R0 by register R1.
*/
public ApfGenerator addDivR1() {
- Instruction instruction = new Instruction(Opcodes.DIV, Register.R1);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.DIV, R1));
}
/**
@@ -645,9 +823,7 @@ public class ApfGenerator {
* and store the result back into register R0.
*/
public ApfGenerator addAndR1() {
- Instruction instruction = new Instruction(Opcodes.AND, Register.R1);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.AND, R1));
}
/**
@@ -655,9 +831,7 @@ public class ApfGenerator {
* and store the result back into register R0.
*/
public ApfGenerator addOrR1() {
- Instruction instruction = new Instruction(Opcodes.OR, Register.R1);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.OR, R1));
}
/**
@@ -665,111 +839,77 @@ public class ApfGenerator {
* register R1.
*/
public ApfGenerator addLeftShiftR1() {
- Instruction instruction = new Instruction(Opcodes.SH, Register.R1);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.SH, R1));
}
/**
* Add an instruction to the end of the program to move {@code value} into {@code register}.
*/
public ApfGenerator addLoadImmediate(Register register, int value) {
- Instruction instruction = new Instruction(Opcodes.LI, register);
- instruction.addSignedImm(value);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.LI, register).addSigned(value));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value equals {@code value}.
*/
- public ApfGenerator addJumpIfR0Equals(int value, String target) {
- Instruction instruction = new Instruction(Opcodes.JEQ);
- instruction.addUnsignedImm(value);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0Equals(int val, String tgt) {
+ return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value does not equal {@code value}.
*/
- public ApfGenerator addJumpIfR0NotEquals(int value, String target) {
- Instruction instruction = new Instruction(Opcodes.JNE);
- instruction.addUnsignedImm(value);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0NotEquals(int val, String tgt) {
+ return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is greater than {@code value}.
*/
- public ApfGenerator addJumpIfR0GreaterThan(int value, String target) {
- Instruction instruction = new Instruction(Opcodes.JGT);
- instruction.addUnsignedImm(value);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0GreaterThan(int val, String tgt) {
+ return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is less than {@code value}.
*/
- public ApfGenerator addJumpIfR0LessThan(int value, String target) {
- Instruction instruction = new Instruction(Opcodes.JLT);
- instruction.addUnsignedImm(value);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0LessThan(int val, String tgt) {
+ return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value has any bits set that are also set in {@code value}.
*/
- public ApfGenerator addJumpIfR0AnyBitsSet(int value, String target) {
- Instruction instruction = new Instruction(Opcodes.JSET);
- instruction.addUnsignedImm(value);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0AnyBitsSet(int val, String tgt) {
+ return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value equals register R1's value.
*/
- public ApfGenerator addJumpIfR0EqualsR1(String target) {
- Instruction instruction = new Instruction(Opcodes.JEQ, Register.R1);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0EqualsR1(String tgt) {
+ return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value does not equal register R1's value.
*/
- public ApfGenerator addJumpIfR0NotEqualsR1(String target) {
- Instruction instruction = new Instruction(Opcodes.JNE, Register.R1);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0NotEqualsR1(String tgt) {
+ return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value is greater than register R1's value.
*/
- public ApfGenerator addJumpIfR0GreaterThanR1(String target) {
- Instruction instruction = new Instruction(Opcodes.JGT, Register.R1);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0GreaterThanR1(String tgt) {
+ return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt));
}
/**
@@ -777,132 +917,104 @@ public class ApfGenerator {
* value is less than register R1's value.
*/
public ApfGenerator addJumpIfR0LessThanR1(String target) {
- Instruction instruction = new Instruction(Opcodes.JLT, Register.R1);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target));
}
/**
* Add an instruction to the end of the program to jump to {@code target} if register R0's
* value has any bits set that are also set in R1's value.
*/
- public ApfGenerator addJumpIfR0AnyBitsSetR1(String target) {
- Instruction instruction = new Instruction(Opcodes.JSET, Register.R1);
- instruction.setTargetLabel(target);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addJumpIfR0AnyBitsSetR1(String tgt) {
+ return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt));
}
/**
- * Add an instruction to the end of the program to jump to {@code target} if the bytes of the
- * packet at an offset specified by {@code register} don't match {@code bytes}, {@code register}
- * must be R0.
+ * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+ * packet at an offset specified by {@code register} don't match {@code bytes}
+ * R=0 means check for not equal
*/
- public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target)
+ public ApfGenerator addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) {
+ return append(new Instruction(Opcodes.JNEBS).addUnsigned(
+ bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
+ }
+
+ /**
+ * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+ * packet at an offset specified by {@code register} match {@code bytes}
+ * R=1 means check for equal.
+ */
+ public ApfGenerator addJumpIfBytesAtR0Equal(byte[] bytes, String tgt)
throws IllegalInstructionException {
- if (register == Register.R1) {
- throw new IllegalInstructionException("JNEBS fails with R1");
- }
- Instruction instruction = new Instruction(Opcodes.JNEBS, register);
- instruction.addUnsignedImm(bytes.length);
- instruction.setTargetLabel(target);
- instruction.setBytesImm(bytes);
- addInstruction(instruction);
- return this;
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(Opcodes.JNEBS, R1).addUnsigned(
+ bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
}
/**
* Add an instruction to the end of the program to load memory slot {@code slot} into
* {@code register}.
*/
- public ApfGenerator addLoadFromMemory(Register register, int slot)
+ public ApfGenerator addLoadFromMemory(Register r, int slot)
throws IllegalInstructionException {
- if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
- throw new IllegalInstructionException("illegal memory slot number: " + slot);
- }
- Instruction instruction = new Instruction(Opcodes.EXT, register);
- instruction.addUnsignedImm(ExtendedOpcodes.LDM.value + slot);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(ExtendedOpcodes.LDM, slot, r));
}
/**
* Add an instruction to the end of the program to store {@code register} into memory slot
* {@code slot}.
*/
- public ApfGenerator addStoreToMemory(Register register, int slot)
+ public ApfGenerator addStoreToMemory(Register r, int slot)
throws IllegalInstructionException {
- if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
- throw new IllegalInstructionException("illegal memory slot number: " + slot);
- }
- Instruction instruction = new Instruction(Opcodes.EXT, register);
- instruction.addUnsignedImm(ExtendedOpcodes.STM.value + slot);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(ExtendedOpcodes.STM, slot, r));
}
/**
* Add an instruction to the end of the program to logically not {@code register}.
*/
- public ApfGenerator addNot(Register register) {
- Instruction instruction = new Instruction(Opcodes.EXT, register);
- instruction.addUnsignedImm(ExtendedOpcodes.NOT.value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addNot(Register r) {
+ return append(new Instruction(ExtendedOpcodes.NOT, r));
}
/**
* Add an instruction to the end of the program to negate {@code register}.
*/
- public ApfGenerator addNeg(Register register) {
- Instruction instruction = new Instruction(Opcodes.EXT, register);
- instruction.addUnsignedImm(ExtendedOpcodes.NEG.value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addNeg(Register r) {
+ return append(new Instruction(ExtendedOpcodes.NEG, r));
}
/**
* Add an instruction to swap the values in register R0 and register R1.
*/
public ApfGenerator addSwap() {
- Instruction instruction = new Instruction(Opcodes.EXT);
- instruction.addUnsignedImm(ExtendedOpcodes.SWAP.value);
- addInstruction(instruction);
- return this;
+ return append(new Instruction(ExtendedOpcodes.SWAP));
}
/**
* Add an instruction to the end of the program to move the value into
* {@code register} from the other register.
*/
- public ApfGenerator addMove(Register register) {
- Instruction instruction = new Instruction(Opcodes.EXT, register);
- instruction.addUnsignedImm(ExtendedOpcodes.MOVE.value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addMove(Register r) {
+ return append(new Instruction(ExtendedOpcodes.MOVE, r));
}
/**
* Add an instruction to the end of the program to let the program immediately return PASS.
*/
- public ApfGenerator addPass() throws IllegalInstructionException {
- Instruction instruction = new Instruction(Opcodes.PASS, Register.R0);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addPass() {
+ // PASS requires using R0 because it shares opcode with DROP
+ return append(new Instruction(Opcodes.PASS));
}
/**
* Add an instruction to the end of the program to increment the counter value and
* immediately return PASS.
*/
- public ApfGenerator addCountAndPass(int counterNumber) throws IllegalInstructionException {
+ public ApfGenerator addCountAndPass(int cnt) throws IllegalInstructionException {
requireApfVersion(MIN_APF_VERSION_IN_DEV);
- checkCounterNumber(counterNumber);
- Instruction instruction = new Instruction(Opcodes.PASS, Register.R0);
- instruction.addUnsignedImm(counterNumber);
- addInstruction(instruction);
- return this;
+ checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
+ 1000 /* upperBound */);
+ // PASS requires using R0 because it shares opcode with DROP
+ return append(new Instruction(Opcodes.PASS).addUnsigned(cnt));
}
/**
@@ -910,35 +1022,52 @@ public class ApfGenerator {
*/
public ApfGenerator addDrop() throws IllegalInstructionException {
requireApfVersion(MIN_APF_VERSION_IN_DEV);
- Instruction instruction = new Instruction(Opcodes.DROP, Register.R1);
- addInstruction(instruction);
- return this;
+ // DROP requires using R1 because it shares opcode with PASS
+ return append(new Instruction(Opcodes.DROP, R1));
}
/**
* Add an instruction to the end of the program to increment the counter value and
* immediately return DROP.
*/
- public ApfGenerator addCountAndDrop(int counterNumber) throws IllegalInstructionException {
+ public ApfGenerator addCountAndDrop(int cnt) throws IllegalInstructionException {
requireApfVersion(MIN_APF_VERSION_IN_DEV);
- checkCounterNumber(counterNumber);
- Instruction instruction = new Instruction(Opcodes.DROP, Register.R1);
- instruction.addUnsignedImm(counterNumber);
- addInstruction(instruction);
- return this;
+ checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
+ 1000 /* upperBound */);
+ // DROP requires using R1 because it shares opcode with PASS
+ return append(new Instruction(Opcodes.DROP, R1).addUnsigned(cnt));
+ }
+
+ /**
+ * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
+ * Buffer length to be allocated is stored in register 0.
+ */
+ public ApfGenerator addAllocateR0() throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.ALLOCATE));
}
/**
* Add an instruction to the end of the program to call the apf_allocate_buffer() function.
*
- * @param register the register value contains the buffer size.
+ * @param size the buffer length to be allocated.
*/
- public ApfGenerator addAlloc(Register register) throws IllegalInstructionException {
- requireApfVersion(5);
- Instruction instruction = new Instruction(Opcodes.EXT, register);
- instruction.addUnsignedImm(ExtendedOpcodes.ALLOC.value);
- addInstruction(instruction);
- return this;
+ public ApfGenerator addAllocate(int size) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ // R1 means the extra be16 immediate is present
+ return append(new Instruction(ExtendedOpcodes.ALLOCATE, R1).addU16(size));
+ }
+
+ /**
+ * Add an instruction to the beginning of the program to reserve the data region.
+ * @param data the actual data byte
+ */
+ public ApfGenerator addData(byte[] data) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ if (!mInstructions.isEmpty()) {
+ throw new IllegalInstructionException("data instruction has to come first");
+ }
+ return append(new Instruction(Opcodes.JMP, R1).addUnsigned(data.length).setBytesImm(data));
}
/**
@@ -946,10 +1075,8 @@ public class ApfGenerator {
*/
public ApfGenerator addTransmit() throws IllegalInstructionException {
requireApfVersion(MIN_APF_VERSION_IN_DEV);
- Instruction instruction = new Instruction(Opcodes.EXT, Register.R0);
- instruction.addUnsignedImm(ExtendedOpcodes.TRANSMIT.value);
- addInstruction(instruction);
- return this;
+ // TRANSMIT requires using R0 because it shares opcode with DISCARD
+ return append(new Instruction(ExtendedOpcodes.TRANSMIT));
}
/**
@@ -957,213 +1084,270 @@ public class ApfGenerator {
*/
public ApfGenerator addDiscard() throws IllegalInstructionException {
requireApfVersion(MIN_APF_VERSION_IN_DEV);
- Instruction instruction = new Instruction(Opcodes.EXT, Register.R1);
- instruction.addUnsignedImm(ExtendedOpcodes.DISCARD.value);
- addInstruction(instruction);
- return this;
+ // DISCARD requires using R1 because it shares opcode with TRANSMIT
+ return append(new Instruction(ExtendedOpcodes.DISCARD, R1));
}
- // TODO: add back when support WRITE opcode
-// /**
-// * Add an instruction to the end of the program to write 1, 2 or 4 bytes value to output
-// buffer.
-// *
-// * @param value the value to write
-// * @param size the size of the value
-// * @return the ApfGenerator object
-// * @throws IllegalInstructionException throws when size is not 1, 2 or 4
-// */
-// public ApfGenerator addWrite(int value, byte size) throws IllegalInstructionException {
-// requireApfVersion(5);
-// if (!(size == 1 || size == 2 || size == 4)) {
-// throw new IllegalInstructionException("length field must be 1, 2 or 4");
-// }
-// if (size < calculateImmSize(value, false)) {
-// throw new IllegalInstructionException(
-// String.format("the value %d is unfit into size: %d", value, size));
-// }
-// Instruction instruction = new Instruction(Opcodes.WRITE);
-// instruction.addUnsignedImm(value, size);
-// addInstruction(instruction);
-// return this;
-// }
-
- // TODO: add back when support EWRITE opcode
-// /**
-// * Add an instruction to the end of the program to write 1, 2 or 4 bytes value from register
-// * to output buffer.
-// *
-// * @param register the register contains the value to be written
-// * @param size the size of the value
-// * @return the ApfGenerator object
-// * @throws IllegalInstructionException throws when size is not 1, 2 or 4
-// */
-// public ApfGenerator addWrite(Register register, byte size)
-// throws IllegalInstructionException {
-// requireApfVersion(5);
-// if (!(size == 1 || size == 2 || size == 4)) {
-// throw new IllegalInstructionException(
-// "length field must be 1, 2 or 4");
-// }
-// Instruction instruction = new Instruction(Opcodes.EXT, register);
-// if (size == 1) {
-// instruction.addUnsignedImm(ExtendedOpcodes.EWRITE1.value);
-// } else if (size == 2) {
-// instruction.addUnsignedImm(ExtendedOpcodes.EWRITE2.value);
-// } else {
-// instruction.addUnsignedImm(ExtendedOpcodes.EWRITE4.value);
-// }
-// addInstruction(instruction);
-// return this;
-// }
-
- // TODO: add back when support PKTCOPY/DATACOPY opcode
-// /**
-// * Add an instruction to the end of the program to copy data from APF data region to output
-// * buffer.
-// *
-// * @param srcOffset the offset inside the APF data region for where to start copy
-// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-// * one time.
-// * @return the ApfGenerator object
-// * @throws IllegalInstructionException throws when imm size is incorrectly set.
-// */
-// public ApfGenerator addDataCopy(int srcOffset, int length)
-// throws IllegalInstructionException {
-// return addMemCopy(srcOffset, length, Register.R1);
-// }
-//
-// /**
-// * Add an instruction to the end of the program to copy data from input packet to output
-// buffer.
-// *
-// * @param srcOffset the offset inside the input packet for where to start copy
-// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-// * one time.
-// * @return the ApfGenerator object
-// * @throws IllegalInstructionException throws when imm size is incorrectly set.
-// */
-// public ApfGenerator addPacketCopy(int srcOffset, int length)
-// throws IllegalInstructionException {
-// return addMemCopy(srcOffset, length, Register.R0);
-// }
-//
-// private ApfGenerator addMemCopy(int srcOffset, int length, Register register)
-// throws IllegalInstructionException {
-// requireApfVersion(5);
-// checkCopyLength(length);
-// checkCopyOffset(srcOffset);
-// Instruction instruction = new Instruction(Opcodes.MEMCOPY, register);
-// // if the offset == 0, it should still be encoded with 1 byte size.
-// if (srcOffset == 0) {
-// instruction.addUnsignedImm(srcOffset, (byte) 1 /* size */);
-// } else {
-// instruction.addUnsignedImm(srcOffset);
-// }
-// instruction.addUnsignedImm(length, (byte) 1 /* size */);
-// addInstruction(instruction);
-// return this;
-// }
-// TODO: add back when support EPKTCOPY/EDATACOPY opcode
-// /**
-// * Add an instruction to the end of the program to copy data from APF data region to output
-// * buffer.
-// *
-// * @param register the register that stored the base offset value.
-// * @param relativeOffset the offset inside the APF data region for where to start copy
-// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-// * one time.
-// * @return the ApfGenerator object
-// * @throws IllegalInstructionException throws when imm size is incorrectly set.
-// */
-// public ApfGenerator addDataCopy(Register register, int relativeOffset, int length)
-// throws IllegalInstructionException {
-// return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EDATACOPY.value);
-// }
-//
-// /**
-// * Add an instruction to the end of the program to copy data from input packet to output
-// buffer.
-// *
-// * @param register the register that stored the base offset value.
-// * @param relativeOffset the offset inside the input packet for where to start copy
-// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-// * one time.
-// * @return the ApfGenerator object
-// * @throws IllegalInstructionException throws when imm size is incorrectly set.
-// */
-// public ApfGenerator addPacketCopy(Register register, int relativeOffset, int length)
-// throws IllegalInstructionException {
-// return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EPKTCOPY.value);
-// }
-//
-// private ApfGenerator addMemcopy(Register register, int relativeOffset, int length, int opcode)
-// throws IllegalInstructionException {
-// requireApfVersion(5);
-// checkCopyLength(length);
-// checkCopyOffset(relativeOffset);
-// Instruction instruction = new Instruction(Opcodes.EXT, register);
-// instruction.addUnsignedImm(opcode);
-// // if the offset == 0, it should still be encoded with 1 byte size.
-// if (relativeOffset == 0) {
-// instruction.addUnsignedImm(relativeOffset, (byte) 1 /* size */);
-// } else {
-// instruction.addUnsignedImm(relativeOffset);
-// }
-// instruction.addUnsignedImm(length, (byte) 1 /* size */);
-// addInstruction(instruction);
-// return this;
-// }
-
- private void checkCopyLength(int length) {
- if (length < 0 || length > 255) {
- throw new IllegalArgumentException(
- "copy length must between 0 to 255, length: " + length);
- }
+ /**
+ * Add an instruction to the end of the program to write 1 byte value to output buffer.
+ */
+ public ApfGenerator addWriteU8(int val) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(Opcodes.WRITE).overrideLenField(1).addU8(val));
+ }
+
+ /**
+ * Add an instruction to the end of the program to write 2 bytes value to output buffer.
+ */
+ public ApfGenerator addWriteU16(int val) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(Opcodes.WRITE).overrideLenField(2).addU16(val));
+ }
+
+ /**
+ * Add an instruction to the end of the program to write 4 bytes value to output buffer.
+ */
+ public ApfGenerator addWriteU32(long val) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(Opcodes.WRITE).overrideLenField(4).addU32(val));
+ }
+
+ /**
+ * Add an instruction to the end of the program to write 1 byte value from register to output
+ * buffer.
+ */
+ public ApfGenerator addWriteU8(Register reg) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.EWRITE1, reg));
+ }
+
+ /**
+ * Add an instruction to the end of the program to write 2 byte value from register to output
+ * buffer.
+ */
+ public ApfGenerator addWriteU16(Register reg) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.EWRITE2, reg));
+ }
+
+ /**
+ * Add an instruction to the end of the program to write 4 byte value from register to output
+ * buffer.
+ */
+ public ApfGenerator addWriteU32(Register reg) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.EWRITE4, reg));
+ }
+
+ /**
+ * Add an instruction to the end of the program to copy data from APF program/data region to
+ * output buffer and auto-increment the output buffer pointer.
+ *
+ * @param src the offset inside the APF program/data region for where to start copy.
+ * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
+ * one time.
+ * @return the ApfGenerator object
+ */
+ public ApfGenerator addDataCopy(int src, int len)
+ throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(Opcodes.PKTDATACOPY, R1).addUnsigned(src).addU8(len));
+ }
+
+ /**
+ * Add an instruction to the end of the program to copy data from input packet to output
+ * buffer and auto-increment the output buffer pointer.
+ *
+ * @param src the offset inside the input packet for where to start copy.
+ * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
+ * one time.
+ * @return the ApfGenerator object
+ */
+ public ApfGenerator addPacketCopy(int src, int len)
+ throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(Opcodes.PKTDATACOPY, R0).addUnsigned(src).addU8(len));
}
- private void checkCopyOffset(int offset) {
- if (offset < 0) {
- throw new IllegalArgumentException(
- "offset must be non less than zero, offset: " + offset);
+ /**
+ * Add an instruction to the end of the program to copy data from APF program/data region to
+ * output buffer and auto-increment the output buffer pointer.
+ * Source offset is stored in R0.
+ *
+ * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
+ * @return the ApfGenerator object
+ */
+ public ApfGenerator addDataCopyFromR0(int len) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.EDATACOPY).addU8(len));
+ }
+
+ /**
+ * Add an instruction to the end of the program to copy data from input packet to output
+ * buffer and auto-increment the output buffer pointer.
+ * Source offset is stored in R0.
+ *
+ * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
+ * @return the ApfGenerator object
+ */
+ public ApfGenerator addPacketCopyFromR0(int len) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.EPKTCOPY).addU8(len));
+ }
+
+ /**
+ * Add an instruction to the end of the program to copy data from APF program/data region to
+ * output buffer and auto-increment the output buffer pointer.
+ * Source offset is stored in R0.
+ * Copy length is stored in R1.
+ *
+ * @return the ApfGenerator object
+ */
+ public ApfGenerator addDataCopyFromR0LenR1() throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.EDATACOPY, R1));
+ }
+
+ /**
+ * Add an instruction to the end of the program to copy data from input packet to output
+ * buffer and auto-increment the output buffer pointer.
+ * Source offset is stored in R0.
+ * Copy length is stored in R1.
+ *
+ * @return the ApfGenerator object
+ */
+ public ApfGenerator addPacketCopyFromR0LenR1() throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ return append(new Instruction(ExtendedOpcodes.EPKTCOPY, R1));
+ }
+
+ /**
+ * Check if the byte is valid dns character: A-Z,0-9,-,_
+ */
+ private static boolean isValidDnsCharacter(byte c) {
+ return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_';
+ }
+
+ private static void validateNames(@NonNull byte[] names) {
+ final int len = names.length;
+ if (len < 4) {
+ throw new IllegalArgumentException("qnames must have at least length 4");
}
+ final String errorMessage = "qname: " + HexDump.toHexString(names)
+ + "is not null-terminated list of TLV-encoded names";
+ int i = 0;
+ while (i < len - 1) {
+ int label_len = names[i++];
+ if (label_len < 1 || label_len > 63) {
+ throw new IllegalArgumentException(
+ "label len: " + label_len + " must be between 1 and 63");
+ }
+ if (i + label_len >= len - 1) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ while (label_len-- > 0) {
+ if (!isValidDnsCharacter(names[i++])) {
+ throw new IllegalArgumentException("qname: " + HexDump.toHexString(names)
+ + " contains invalid character");
+ }
+ }
+ if (names[i] == 0) {
+ i++; // skip null terminator.
+ }
+ }
+ if (names[len - 1] != 0) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ }
+
+ /**
+ * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+ * payload's DNS questions do NOT contain the QNAME specified in {@code qnames} and qtype
+ * equals {@code qtype}. Examines the payload starting at the offset in R0.
+ * R = 0 means check for "does not contain".
+ */
+ public ApfGenerator addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype,
+ @NonNull String tgt) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ validateNames(qnames);
+ return append(new Instruction(ExtendedOpcodes.JDNSQMATCH).setTargetLabel(tgt).addU8(
+ qtype).setBytesImm(qnames));
+ }
+
+ /**
+ * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+ * payload's DNS questions contain the QNAME specified in {@code qnames} and qtype
+ * equals {@code qtype}. Examines the payload starting at the offset in R0.
+ * R = 1 means check for "contain".
+ */
+ public ApfGenerator addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype,
+ @NonNull String tgt) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ validateNames(qnames);
+ return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, R1).setTargetLabel(tgt).addU8(
+ qtype).setBytesImm(qnames));
+ }
+
+ /**
+ * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+ * payload's DNS answers/authority/additional records do NOT contain the NAME
+ * specified in {@code Names}. Examines the payload starting at the offset in R0.
+ * R = 0 means check for "does not contain".
+ */
+ public ApfGenerator addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names,
+ @NonNull String tgt) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ validateNames(names);
+ return append(new Instruction(ExtendedOpcodes.JDNSAMATCH).setTargetLabel(tgt).setBytesImm(
+ names));
}
- private void checkCounterNumber(int counterNumber) {
- if (counterNumber < 1 || counterNumber > 1000) {
- throw new IllegalArgumentException(
- "Counter number must be in range (0, 1000], counterNumber: " + counterNumber);
+ /**
+ * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP
+ * payload's DNS answers/authority/additional records contain the NAME
+ * specified in {@code Names}. Examines the payload starting at the offset in R0.
+ * R = 1 means check for "contain".
+ */
+ public ApfGenerator addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names,
+ @NonNull String tgt) throws IllegalInstructionException {
+ requireApfVersion(MIN_APF_VERSION_IN_DEV);
+ validateNames(names);
+ return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, R1).setTargetLabel(
+ tgt).setBytesImm(names));
+ }
+
+ private static void checkRange(@NonNull String variableName, long value, long lowerBound,
+ long upperBound) {
+ if (value >= lowerBound && value <= upperBound) {
+ return;
}
+ throw new IllegalArgumentException(
+ String.format("%s: %d, must be in range [%d, %d]", variableName, value, lowerBound,
+ upperBound));
}
/**
* Add an instruction to the end of the program to load 32 bits from the data memory into
* {@code register}. The source address is computed by adding the signed immediate
* @{code offset} to the other register.
- * Requires APF v3 or greater.
+ * Requires APF v4 or greater.
*/
- public ApfGenerator addLoadData(Register destinationRegister, int offset)
+ public ApfGenerator addLoadData(Register dst, int ofs)
throws IllegalInstructionException {
- requireApfVersion(3);
- Instruction instruction = new Instruction(Opcodes.LDDW, destinationRegister);
- instruction.addSignedImm(offset);
- addInstruction(instruction);
- return this;
+ requireApfVersion(APF_VERSION_4);
+ return append(new Instruction(Opcodes.LDDW, dst).addSigned(ofs));
}
/**
* Add an instruction to the end of the program to store 32 bits from {@code register} into the
* data memory. The destination address is computed by adding the signed immediate
* @{code offset} to the other register.
- * Requires APF v3 or greater.
+ * Requires APF v4 or greater.
*/
- public ApfGenerator addStoreData(Register sourceRegister, int offset)
+ public ApfGenerator addStoreData(Register src, int ofs)
throws IllegalInstructionException {
- requireApfVersion(3);
- Instruction instruction = new Instruction(Opcodes.STDW, sourceRegister);
- instruction.addSignedImm(offset);
- addInstruction(instruction);
- return this;
+ requireApfVersion(APF_VERSION_4);
+ return append(new Instruction(Opcodes.STDW, src).addSigned(ofs));
}
/**
@@ -1182,7 +1366,7 @@ public class ApfGenerator {
/**
* Calculate the size of the imm.
*/
- private static byte calculateImmSize(int imm, boolean signed) {
+ private static int calculateImmSize(int imm, boolean signed) {
if (imm == 0) {
return 0;
}
diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java
index 0300d349..5bd2515b 100644
--- a/src/android/net/apf/DnsUtils.java
+++ b/src/android/net/apf/DnsUtils.java
@@ -301,7 +301,7 @@ public class DnsUtils {
gen.addJumpIfR0NotEquals(label.length(), noMatchLabel);
gen.addLoadFromMemory(R0, SLOT_CURRENT_PARSE_OFFSET);
gen.addAdd(1);
- gen.addJumpIfBytesNotEqual(R0, label.getBytes(), noMatchLabel);
+ gen.addJumpIfBytesAtR0NotEqual(label.getBytes(), noMatchLabel);
// Prep offset of next label.
gen.addAdd(label.length());
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java
index cc09856d..6b93d89f 100644
--- a/src/android/net/apf/LegacyApfFilter.java
+++ b/src/android/net/apf/LegacyApfFilter.java
@@ -982,7 +982,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// Generate code to match the packet bytes.
if (section.type == PacketSection.Type.MATCH) {
gen.addLoadImmediate(Register.R0, section.start);
- gen.addJumpIfBytesNotEqual(Register.R0,
+ gen.addJumpIfBytesAtR0NotEqual(
Arrays.copyOfRange(mPacket.array(), section.start,
section.start + section.length),
nextFilterLabel);
@@ -1065,7 +1065,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
// A NAT-T keepalive packet contains 1 byte payload with the value 0xff
// Check payload length is 1
@@ -1080,11 +1080,11 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// Check that the ports match
gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
gen.addAdd(ETH_HEADER_LEN);
- gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
// Payload offset = R0 + UDP header length
gen.addAdd(UDP_HEADER_LEN);
- gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE);
gen.addJump(mCountAndDropLabel);
@@ -1180,7 +1180,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
// Skip to the next filter if it's not zero-sized :
// TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
@@ -1202,7 +1202,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN);
gen.addAddR1();
- gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
gen.addJump(mCountAndDropLabel);
@@ -1316,7 +1316,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// Pass if not ARP IPv4.
gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
- gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel);
// Pass if unknown ARP opcode.
gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
@@ -1332,7 +1332,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// Pass if unicast reply.
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
- gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
// Either a unicast request, a unicast reply, or a broadcast reply.
gen.defineLabel(checkTargetIPv4);
@@ -1346,7 +1346,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// and broadcast replies with a different target IPv4 address.
gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
- gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
+ gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
}
maybeSetupCounter(gen, Counter.PASSED_ARP);
@@ -1394,7 +1394,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
// NOTE: Relies on R1 containing IPv4 header offset.
gen.addAddR1();
- gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
+ gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
maybeSetupCounter(gen, Counter.PASSED_DHCP);
gen.addJump(mCountAndPassLabel);
@@ -1428,7 +1428,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// TODO: can we invert this condition to fall through to the common pass case below?
maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
gen.addJump(mCountAndDropLabel);
} else {
@@ -1555,8 +1555,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// TODO: Drop only if they don't contain the address of on-link neighbours.
final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix,
- skipUnsolicitedMulticastNALabel);
+ gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
gen.addJump(mCountAndDropLabel);
@@ -1613,7 +1612,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// Check it's L2 mDNS multicast address.
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
- gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
+ gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
skipMdnsv4Filter);
// Checks it's IPv4.
@@ -1631,8 +1630,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// Checks it's L2 mDNS multicast address.
// Relies on R0 containing the ethernet destination mac address offset.
- gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS,
- skipMdnsFilter);
+ gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
// Checks it's IPv6.
gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
@@ -1663,7 +1661,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
for (int i = 0; i < mMdnsAllowList.size(); ++i) {
final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i;
final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i));
- gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck);
+ gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck);
// QNAME matched
gen.addJump(mDnsAcceptPacket);
// QNAME not matched
@@ -1777,7 +1775,7 @@ public class LegacyApfFilter implements AndroidPacketFilter {
// Drop non-IP non-ARP broadcasts, pass the rest
gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
- gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+ gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
gen.addJump(mCountAndDropLabel);
diff --git a/src/android/net/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java
index 291a97a0..8d53048f 100644
--- a/src/android/net/dhcp6/Dhcp6Client.java
+++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -20,9 +20,7 @@ import static android.net.dhcp6.Dhcp6Packet.IAID;
import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IFA_F_NODAD;
import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_NONBLOCK;
@@ -31,14 +29,8 @@ import static com.android.net.module.util.NetworkStackConstants.DHCP6_CLIENT_POR
import static com.android.net.module.util.NetworkStackConstants.DHCP6_SERVER_PORT;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
-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.createInet6AddressFromEui64;
-import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64;
import android.content.Context;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
import android.net.ip.IpClient;
import android.net.util.SocketUtils;
import android.os.Handler;
@@ -58,12 +50,10 @@ import com.android.internal.util.WakeupMessage;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.PacketReader;
-import com.android.net.module.util.netlink.NetlinkUtils;
import com.android.net.module.util.structs.IaPrefixOption;
import java.io.FileDescriptor;
import java.io.IOException;
-import java.net.Inet6Address;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Collections;
@@ -95,8 +85,6 @@ public class Dhcp6Client extends StateMachine {
// Message.arg1 arguments to CMD_DHCP6_RESULT notification
public static final int DHCP6_PD_SUCCESS = 1;
public static final int DHCP6_PD_PREFIX_EXPIRED = 2;
- public static final int DHCP6_PD_PREFIX_CHANGED = 3;
- public static final int DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED = 4;
// Notification from DHCPv6 state machine before quitting
public static final int CMD_ON_QUIT = PUBLIC_BASE + 4;
@@ -436,8 +424,8 @@ public class Dhcp6Client extends StateMachine {
Log.d(TAG, "Scheduling IA_PD expiry in " + expirationTimeout + "s");
}
- private void notifyPrefixDelegation(int result, @Nullable final PrefixDelegation pd) {
- mController.sendMessage(CMD_DHCP6_RESULT, result, 0, pd);
+ private void notifyPrefixDelegation(int result, @Nullable final List<IaPrefixOption> ipos) {
+ mController.sendMessage(CMD_DHCP6_RESULT, result, 0, ipos);
}
private void clearDhcp6State() {
@@ -557,10 +545,15 @@ public class Dhcp6Client extends StateMachine {
return sendSolicitPacket(transId, elapsedTimeMs, pd.build());
}
- // TODO: support multiple prefixes.
@Override
protected void receivePacket(Dhcp6Packet packet) {
final PrefixDelegation pd = packet.mPrefixDelegation;
+ // Ignore any Advertise or Reply for Solicit(with Rapid Commit) with NoPrefixAvail
+ // status code, retransmit Solicit to see if any valid response from other Servers.
+ if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) {
+ Log.w(TAG, "Server responded to Solicit without available prefix, ignoring");
+ return;
+ }
if (packet instanceof Dhcp6AdvertisePacket) {
Log.d(TAG, "Get prefix delegation option from Advertise: " + pd);
mAdvertise = pd;
@@ -601,6 +594,11 @@ public class Dhcp6Client extends StateMachine {
protected void receivePacket(Dhcp6Packet packet) {
if (!(packet instanceof Dhcp6ReplyPacket)) return;
final PrefixDelegation pd = packet.mPrefixDelegation;
+ if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) {
+ Log.w(TAG, "Server responded to Request without available prefix, restart Solicit");
+ transitionTo(mSolicitState);
+ return;
+ }
Log.d(TAG, "Get prefix delegation option from Reply: " + pd);
mReply = pd;
mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs);
@@ -621,7 +619,7 @@ public class Dhcp6Client extends StateMachine {
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_DHCP6_PD_EXPIRE:
- notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, null);
+ notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, mReply.getValidIaPrefixes());
transitionTo(mSolicitState);
return HANDLED;
default:
@@ -639,33 +637,6 @@ public class Dhcp6Client extends StateMachine {
}
}
- // Create an IPv6 address from the interface mac address with IFA_F_MANAGETEMPADDR
- // flag, kernel will create another privacy IPv6 address on behalf of user space.
- // We don't need to remember IPv6 addresses that need to extend the lifetime every
- // time it enters BoundState.
- private boolean addInterfaceAddress(@NonNull final Inet6Address address,
- @NonNull final IaPrefixOption ipo) {
- final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD;
- final long now = SystemClock.elapsedRealtime();
- final long deprecationTime = now + ipo.preferred;
- final long expirationTime = now + ipo.valid;
- final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags,
- RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime);
- if (!la.isGlobalPreferred()) {
- Log.e(TAG, la + " is not a global preferred IPv6 address");
- return false;
- }
- if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address,
- (short) RFC7421_PREFIX_LENGTH,
- flags, (byte) RT_SCOPE_UNIVERSE /* scope */,
- ipo.preferred, ipo.valid)) {
- Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress()
- + "%" + mIface.index);
- return false;
- }
- return true;
- }
-
/**
* Client has already obtained the lease(e.g. IA_PD option) from server and stays in Bound
* state until T1 expires, and then transition to Renew state to extend the lease duration.
@@ -675,26 +646,9 @@ public class Dhcp6Client extends StateMachine {
public void enter() {
super.enter();
scheduleLeaseTimers();
-
- // TODO: roll back to SOLICIT state after a delay if something wrong happens
- // instead of returning directly.
- for (IaPrefixOption ipo : mReply.getValidIaPrefixes()) {
- // TODO: The prefix with preferred/valid lifetime of 0 is valid, but client
- // should stop using the prefix immediately. Actually kernel doesn't accept
- // the address with valid lifetime of 0 and returns EINVAL when it sees that.
- // We should send RTM_DELADDR netlink message to kernel to delete these addresses
- // from the interface if any.
- // Configure IPv6 addresses based on the delegated prefix(es) on the interface.
- // We've checked that delegated prefix is valid upon receiving the response from
- // DHCPv6 server, and the server may assign a prefix with length less than 64. So
- // for SLAAC use case we always set the prefix length to 64 even if the delegated
- // prefix length is less than 64.
- final IpPrefix prefix = ipo.getIpPrefix();
- final Inet6Address address = createInet6AddressFromEui64(prefix,
- macAddressToEui64(mIface.macAddr));
- if (!addInterfaceAddress(address, ipo)) continue;
- }
- notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply);
+ // Pass valid delegated prefix(es) to IpClient for IPv6 address configuration and
+ // active prefix(es) maintenance.
+ notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply.getValidIaPrefixes());
}
@Override
@@ -723,8 +677,8 @@ public class Dhcp6Client extends StateMachine {
* That forces previous delegated prefixes to expire in a natural way, and client should
* also stop trying to extend the lifetime for them. That being said, the global IPv6 address
* lifetime won't be updated in BoundState if corresponding prefix doesn't appear in Reply
- * message, resulting in these global IPv6 addresses eventually and IpClient obtains these
- * updates via netlink message and remove the delegated prefix(es) from LinkProperties.
+ * message, resulting in these global IPv6 addresses expire eventually and IpClient obtains
+ * these updates via netlink message and remove the delegated prefix(es) from LinkProperties.
* - If some binding IA_PDs were absent in Reply message, client should still stay at RenewState
* or RebindState and retransmit Renew/Rebind messages to see if it can get all later. So far
* we only support one IA_PD option per interface, if the received Reply message doesn't take
@@ -744,9 +698,15 @@ public class Dhcp6Client extends StateMachine {
@Override
protected void receivePacket(Dhcp6Packet packet) {
if (!(packet instanceof Dhcp6ReplyPacket)) return;
+ final PrefixDelegation pd = packet.mPrefixDelegation;
+ // Stay at Renew/Rebind state if the Reply message takes NoPrefixAvail status code,
+ // retransmit Renew/Rebind message to server, to retry obtaining the prefixes.
+ if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) {
+ Log.w(TAG, "Server responded to Renew/Rebind without available prefix, ignoring");
+ return;
+ }
// TODO: send a Request message to the server that responded if any of the IA_PDs in
// Reply message contain NoBinding status code.
- final PrefixDelegation pd = packet.mPrefixDelegation;
Log.d(TAG, "Get prefix delegation option from Reply as response to Renew/Rebind " + pd);
if (pd.ipos.isEmpty()) return;
mReply = pd;
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java
index 4ef3195d..53dd274d 100644
--- a/src/android/net/dhcp6/Dhcp6Packet.java
+++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -32,7 +32,6 @@ import com.android.net.module.util.structs.IaPrefixOption;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -95,16 +94,16 @@ public class Dhcp6Packet {
* DHCPv6 Optional Type: Status Code.
*/
public static final byte DHCP6_STATUS_CODE = 13;
+ private static final byte MIN_STATUS_CODE_OPT_LEN = 6;
protected short mStatusCode;
- protected String mStatusMsg;
public static final short STATUS_SUCCESS = 0;
public static final short STATUS_UNSPEC_FAIL = 1;
- public static final short STATUS_NO_ADDR_AVAI = 2;
+ public static final short STATUS_NO_ADDRS_AVAIL = 2;
public static final short STATUS_NO_BINDING = 3;
- public static final short STATUS_PREFIX_NOT_ONLINK = 4;
+ public static final short STATUS_NOT_ONLINK = 4;
public static final short STATUS_USE_MULTICAST = 5;
- public static final short STATUS_NO_PREFIX_AVAI = 6;
+ public static final short STATUS_NO_PREFIX_AVAIL = 6;
/**
* DHCPv6 zero-length Optional Type: Rapid Commit. Per RFC4039, both DHCPDISCOVER and DHCPACK
@@ -209,14 +208,22 @@ public class Dhcp6Packet {
public final int t2;
@NonNull
public final List<IaPrefixOption> ipos;
+ public final short statusCode;
+ @VisibleForTesting
public PrefixDelegation(int iaid, int t1, int t2,
- @NonNull final List<IaPrefixOption> ipos) {
+ @NonNull final List<IaPrefixOption> ipos, short statusCode) {
Objects.requireNonNull(ipos);
this.iaid = iaid;
this.t1 = t1;
this.t2 = t2;
this.ipos = ipos;
+ this.statusCode = statusCode;
+ }
+
+ public PrefixDelegation(int iaid, int t1, int t2,
+ @NonNull final List<IaPrefixOption> ipos) {
+ this(iaid, t1, t2, ipos, STATUS_SUCCESS /* statusCode */);
}
/**
@@ -251,6 +258,7 @@ public class Dhcp6Packet {
final int t1 = buffer.getInt();
final int t2 = buffer.getInt();
final List<IaPrefixOption> ipos = new ArrayList<IaPrefixOption>();
+ short statusCode = STATUS_SUCCESS;
while (buffer.remaining() > 0) {
final int original = buffer.position();
final short optionType = buffer.getShort();
@@ -262,12 +270,18 @@ public class Dhcp6Packet {
Log.d(TAG, "IA Prefix Option: " + ipo);
ipos.add(ipo);
break;
- // TODO: support DHCP6_STATUS_CODE option
+ case DHCP6_STATUS_CODE:
+ statusCode = buffer.getShort();
+ // Skip the status message if any.
+ if (optionLen > 2) {
+ skipOption(buffer, optionLen - 2);
+ }
+ break;
default:
skipOption(buffer, optionLen);
}
}
- return new PrefixDelegation(iaid, t1, t2, ipos);
+ return new PrefixDelegation(iaid, t1, t2, ipos, statusCode);
} catch (BufferUnderflowException e) {
throw new ParseException(e.getMessage());
}
@@ -282,16 +296,26 @@ public class Dhcp6Packet {
/**
* Build an IA_PD option from given specific parameters, including IA_PREFIX options.
+ *
+ * Per RFC8415 section 21.13 if the Status Code option does not appear in a message in
+ * which the option could appear, the status of the message is assumed to be Success. So
+ * only put the Status Code option in IA_PD when the status code is not Success.
*/
public ByteBuffer build(@NonNull final List<IaPrefixOption> input) {
final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH
- + Struct.getSize(IaPrefixOption.class) * input.size());
+ + Struct.getSize(IaPrefixOption.class) * input.size()
+ + (statusCode != STATUS_SUCCESS ? MIN_STATUS_CODE_OPT_LEN : 0));
iapd.putInt(iaid);
iapd.putInt(t1);
iapd.putInt(t2);
for (IaPrefixOption ipo : input) {
ipo.writeToByteBuffer(iapd);
}
+ if (statusCode != STATUS_SUCCESS) {
+ iapd.putShort(DHCP6_STATUS_CODE);
+ iapd.putShort((short) 2);
+ iapd.putShort(statusCode);
+ }
iapd.flip();
return iapd;
}
@@ -314,8 +338,8 @@ public class Dhcp6Packet {
@Override
public String toString() {
- return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2
- + ", IA prefix options: " + ipos;
+ return String.format("Prefix Delegation, iaid: %s, t1: %s, t2: %s, status code: %s,"
+ + " IA prefix options: %s", iaid, t1, t2, statusCodeToString(statusCode), ipos);
}
/**
@@ -366,6 +390,27 @@ public class Dhcp6Packet {
}
}
+ private static String statusCodeToString(short statusCode) {
+ switch (statusCode) {
+ case STATUS_SUCCESS:
+ return "Success";
+ case STATUS_UNSPEC_FAIL:
+ return "UnspecFail";
+ case STATUS_NO_ADDRS_AVAIL:
+ return "NoAddrsAvail";
+ case STATUS_NO_BINDING:
+ return "NoBinding";
+ case STATUS_NOT_ONLINK:
+ return "NotOnLink";
+ case STATUS_USE_MULTICAST:
+ return "UseMulticast";
+ case STATUS_NO_PREFIX_AVAIL:
+ return "NoPrefixAvail";
+ default:
+ return "Unknown";
+ }
+ }
+
private static void skipOption(@NonNull final ByteBuffer packet, int optionLen)
throws BufferUnderflowException {
for (int i = 0; i < optionLen; i++) {
@@ -374,35 +419,6 @@ public class Dhcp6Packet {
}
/**
- * Reads a string of specified length from the buffer.
- *
- * TODO: move to a common place which can be shared with DhcpClient.
- */
- private static String readAsciiString(@NonNull final ByteBuffer buf, int byteCount,
- boolean isNullOk) {
- final byte[] bytes = new byte[byteCount];
- buf.get(bytes);
- return readAsciiString(bytes, isNullOk);
- }
-
- private static String readAsciiString(@NonNull final byte[] payload, boolean isNullOk) {
- final byte[] bytes = payload;
- int length = bytes.length;
- if (!isNullOk) {
- // Stop at the first null byte. This is because some DHCP options (e.g., the domain
- // name) are passed to netd via FrameworkListener, which refuses arguments containing
- // null bytes. We don't do this by default because vendorInfo is an opaque string which
- // could in theory contain null bytes.
- for (length = 0; length < bytes.length; length++) {
- if (bytes[length] == 0) {
- break;
- }
- }
- }
- return new String(bytes, 0, length, StandardCharsets.US_ASCII);
- }
-
- /**
* Creates a concrete Dhcp6Packet from the supplied ByteBuffer.
*
* The buffer only starts with a UDP encapsulation (i.e. DHCPv6 message). A subset of the
@@ -426,7 +442,6 @@ public class Dhcp6Packet {
byte[] serverDuid = null;
byte[] clientDuid = null;
short statusCode = STATUS_SUCCESS;
- String statusMsg = null;
boolean rapidCommit = false;
int solMaxRt = 0;
PrefixDelegation pd = null;
@@ -487,7 +502,12 @@ public class Dhcp6Packet {
case DHCP6_STATUS_CODE:
expectedLen = optionLen;
statusCode = packet.getShort();
- statusMsg = readAsciiString(packet, expectedLen - 2, false /* isNullOk */);
+ // Skip the status message (if any), which is a UTF-8 encoded text string
+ // suitable for display to the end user, but is not useful for Dhcp6Client
+ // to decide how to properly handle the status code.
+ if (optionLen - 2 > 0) {
+ skipOption(packet, optionLen - 2);
+ }
break;
case DHCP6_SOL_MAX_RT:
expectedLen = 4;
@@ -545,7 +565,6 @@ public class Dhcp6Packet {
throw new ParseException("Missing IA_PD option");
}
newPacket.mStatusCode = statusCode;
- newPacket.mStatusMsg = statusMsg;
newPacket.mRapidCommit = rapidCommit;
newPacket.mSolMaxRt =
(solMaxRt >= 60 && solMaxRt <= 86400)
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 4f8eead7..d7ef4df2 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -19,7 +19,6 @@ package android.net.ip;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
-import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation;
import static android.net.ip.IIpClient.PROV_IPV4_DISABLED;
import static android.net.ip.IIpClient.PROV_IPV6_DISABLED;
import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL;
@@ -32,22 +31,28 @@ import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.ETH_P_ARP;
import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IFA_F_NODAD;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;
+import static com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
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_LIGHT_DOZE_FORCE_DISABLE;
import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_FORCE_DISABLE;
+import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64;
+import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64;
import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
import android.annotation.SuppressLint;
@@ -123,8 +128,10 @@ import com.android.internal.util.StateMachine;
import com.android.internal.util.WakeupMessage;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.ConnectivityUtils;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.LinkPropertiesUtils;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.SocketUtils;
import com.android.net.module.util.arp.ArpPacket;
@@ -154,7 +161,6 @@ import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URL;
-import java.net.UnknownHostException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -541,6 +547,8 @@ public class IpClient extends StateMachine {
// IpClient shares a handler with Dhcp6Client: commands must not overlap
public static final int DHCP6CLIENT_CMD_BASE = 2000;
+ private static final int DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS =
+ IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE | IFA_F_NODAD;
// Settings and default values.
private static final int MAX_LOG_RECORDS = 500;
@@ -677,6 +685,8 @@ public class IpClient extends StateMachine {
private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>();
// Set of IPv6 addresses from which multicast NS packets have been sent.
private final Set<Inet6Address> mMulticastNsSourceAddresses = new HashSet<>();
+ // Set of delegated prefixes.
+ private final Set<IpPrefix> mDelegatedPrefixes = new HashSet<>();
@Nullable
private final DevicePolicyManager mDevicePolicyManager;
@@ -694,7 +704,7 @@ public class IpClient extends StateMachine {
private final boolean mUseNewApfFilter;
private final boolean mEnableIpClientIgnoreLowRaLifetime;
private final boolean mApfShouldHandleLightDoze;
- private final boolean mApfShouldPollingCounters;
+ private final boolean mEnableApfPollingCounters;
private InterfaceParams mInterfaceParams;
@@ -721,7 +731,6 @@ public class IpClient extends StateMachine {
private Integer mDadTransmits = null;
private int mMaxDtimMultiplier = DTIM_MULTIPLIER_RESET;
private ApfCapabilities mCurrentApfCapabilities;
- private PrefixDelegation mPrefixDelegation;
private WakeupMessage mIpv6AutoconfTimeoutAlarm = null;
/**
@@ -928,13 +937,13 @@ public class IpClient extends StateMachine {
CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS,
DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS;
mUseNewApfFilter = mDependencies.isFeatureEnabled(context, APF_NEW_RA_FILTER_VERSION);
+ mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context,
+ APF_POLLING_COUNTERS_VERSION);
mEnableIpClientIgnoreLowRaLifetime = 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);
- mApfShouldPollingCounters = mDependencies.isFeatureNotChickenedOut(
- mContext, APF_POLLING_COUNTERS_FORCE_DISABLE);
IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration(
mMinRdnssLifetimeSec);
@@ -1135,10 +1144,6 @@ public class IpClient extends StateMachine {
return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION);
}
- private boolean isMulticastNsEnabled() {
- return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_MULTICAST_NS_VERSION);
- }
-
@VisibleForTesting
static MacAddress getInitialBssid(final Layer2Information layer2Info,
final ScanResultInfo scanResultInfo, boolean isAtLeastS) {
@@ -1460,7 +1465,6 @@ public class IpClient extends StateMachine {
mDhcpResults = null;
mTcpBufferSizes = "";
mHttpProxy = null;
- mPrefixDelegation = null;
mLinkProperties = new LinkProperties();
mLinkProperties.setInterfaceName(mInterfaceName);
@@ -1741,6 +1745,31 @@ public class IpClient extends StateMachine {
addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
mShim.setNat64Prefix(newLp, mShim.getNat64Prefix(netlinkLinkProperties));
+ // Check if any link address update from netlink.
+ final CompareResult<LinkAddress> results =
+ LinkPropertiesUtils.compareAddresses(mLinkProperties, newLp);
+ // In the case that there are multiple netlink update events about a global IPv6 address
+ // derived from the delegated prefix, a flag-only change event(e.g. due to the duplicate
+ // address detection) will cause an identical IP address to be put into both Added and
+ // Removed list based on the CompareResult implementation. To prevent a prefix from being
+ // mistakenly removed from the delegate prefix list, it is better to always check the
+ // removed list before checking the added list(e.g. anyway we can add the removed prefix
+ // back again).
+ for (LinkAddress la : results.removed) {
+ if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) {
+ final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH);
+ mDelegatedPrefixes.remove(prefix);
+ }
+ // TODO: remove onIpv6AddressRemoved callback.
+ }
+
+ for (LinkAddress la : results.added) {
+ if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) {
+ final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH);
+ mDelegatedPrefixes.add(prefix);
+ }
+ }
+
// [3] Add in data from DHCPv4, if available.
//
// mDhcpResults is never shared with any other owner so we don't have
@@ -1777,26 +1806,22 @@ public class IpClient extends StateMachine {
// TODO: also look at the IPv6 RA (netlink) for captive portal URL
}
- // [4] Add in data from DHCPv6 Prefix Delegation, if available.
- if (mPrefixDelegation != null) {
- for (IaPrefixOption ipo : mPrefixDelegation.ipos) {
- try {
- final IpPrefix destination =
- new IpPrefix(Inet6Address.getByAddress(ipo.prefix), ipo.prefixLen);
- // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to this route
- // based on the delegated prefix. To prevent the traffic loop between host and
- // upstream delegated router. Because we specify the IFA_F_NOPREFIXROUTE when
- // adding the IPv6 address, the kernel does not create a delegated prefix route,
- // as a result, the user space won't receive any RTM_NEWROUTE message about the
- // delegated prefix, we still need to install an unreachable route for the
- // delegated prefix manually in LinkProperties to notify the caller this update.
- // TODO: support RTN_BLACKHOLE in netd and use that on newer Android versions.
- final RouteInfo route = new RouteInfo(destination, null /* gateway */,
- mInterfaceName, RTN_UNREACHABLE);
- newLp.addRoute(route);
- } catch (UnknownHostException e) {
- Log.wtf(mTag, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix));
- }
+ // [4] Add route with delegated prefix according to the global address update.
+ if (mDhcp6PrefixDelegationEnabled) {
+ for (IpPrefix destination : mDelegatedPrefixes) {
+ // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to
+ // this route based on the delegated prefix. To prevent the traffic loop
+ // between host and upstream delegated router. Because we specify the
+ // IFA_F_NOPREFIXROUTE when adding the IPv6 address, the kernel does not
+ // create a delegated prefix route, as a result, the user space won't
+ // receive any RTM_NEWROUTE message about the delegated prefix, we still
+ // need to install an unreachable route for the delegated prefix manually
+ // in LinkProperties to notify the caller this update.
+ // TODO: support RTN_BLACKHOLE in netd and use that on newer Android
+ // versions.
+ final RouteInfo route = new RouteInfo(destination,
+ null /* gateway */, mInterfaceName, RTN_UNREACHABLE);
+ newLp.addRoute(route);
}
}
@@ -1994,6 +2019,24 @@ public class IpClient extends StateMachine {
}
}
+ private static boolean hasFlag(@NonNull final LinkAddress la, final int flags) {
+ return (la.getFlags() & flags) == flags;
+
+ }
+
+ // Check whether a global IPv6 stable address is derived from DHCPv6 prefix delegation.
+ // Address derived from delegated prefix should be:
+ // - unicast global routable address
+ // - with prefix length of 64
+ // - has IFA_F_MANAGETEMPADDR, IFA_F_NOPREFIXROUTE and IFA_F_NODAD flags
+ private static boolean isIpv6StableDelegatedAddress(@NonNull final LinkAddress la) {
+ return la.isIpv6()
+ && !ConnectivityUtils.isIPv6ULA(la.getAddress())
+ && (la.getPrefixLength() == RFC7421_PREFIX_LENGTH)
+ && (la.getScope() == (byte) RT_SCOPE_UNIVERSE)
+ && hasFlag(la, DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS);
+ }
+
// Returns false if we have lost provisioning, true otherwise.
private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
final LinkProperties newLp = assembleLinkProperties();
@@ -2035,9 +2078,7 @@ public class IpClient extends StateMachine {
//
// TODO: stop sending this multicast NS after deployment of RFC9131 in the field, leverage
// the gratuitous NA to update the first-hop router's neighbor cache entry.
- if (isMulticastNsEnabled()) {
- maybeSendMulticastNSes(newLp);
- }
+ maybeSendMulticastNSes(newLp);
// Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
// wait for the provisioning completion and record the latency.
@@ -2441,6 +2482,7 @@ public class IpClient extends StateMachine {
mHasDisabledAcceptRaDefrtrOnProvLoss = false;
mGratuitousNaTargetAddresses.clear();
mMulticastNsSourceAddresses.clear();
+ mDelegatedPrefixes.clear();
resetLinkProperties();
if (mStartTimeMillis > 0) {
@@ -2903,7 +2945,7 @@ public class IpClient extends StateMachine {
if (mApfFilter == null) {
mCallback.setFallbackMulticastFilter(mMulticastFiltering);
}
- if (mApfShouldPollingCounters) {
+ if (mEnableApfPollingCounters) {
sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs);
}
@@ -2998,27 +3040,68 @@ public class IpClient extends StateMachine {
}
}
- private void clearIpv6PrefixDelegationAddresses() {
- if (mPrefixDelegation == null) {
- Log.wtf(mTag, "PrefixDelegation shouldn't be null when DHCPv6 PD fails.");
- return;
- }
- final IaPrefixOption ipo = mPrefixDelegation.ipos.get(0);
- final IpPrefix prefix;
- try {
- prefix = new IpPrefix(Inet6Address.getByAddress(ipo.prefix), RFC7421_PREFIX_LENGTH);
- } catch (UnknownHostException e) {
- Log.wtf(TAG, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix));
- return;
- }
-
- // Delete the global IPv6 address based on delegated prefix from interface.
+ private void deleteIpv6PrefixDelegationAddresses(final IpPrefix prefix) {
for (LinkAddress la : mLinkProperties.getLinkAddresses()) {
final InetAddress address = la.getAddress();
if (prefix.contains(address)) {
- NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index,
- (Inet6Address) address, (short) la.getPrefixLength());
+ if (!NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index,
+ (Inet6Address) address, (short) la.getPrefixLength())) {
+ Log.e(TAG, "Failed to delete IPv6 address " + address.getHostAddress());
+ }
+ }
+ }
+ }
+
+ private void addInterfaceAddress(@NonNull final Inet6Address address,
+ @NonNull final IaPrefixOption ipo) {
+ final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD;
+ final long now = SystemClock.elapsedRealtime();
+ final long deprecationTime = now + ipo.preferred;
+ final long expirationTime = now + ipo.valid;
+ final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags,
+ RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime);
+ if (!la.isGlobalPreferred()) {
+ Log.w(TAG, la + " is not a global IPv6 address");
+ return;
+ }
+ if (!NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index, address,
+ (short) RFC7421_PREFIX_LENGTH,
+ flags, (byte) RT_SCOPE_UNIVERSE /* scope */,
+ ipo.preferred, ipo.valid)) {
+ Log.e(TAG, "Failed to set IPv6 address on " + address.getHostAddress()
+ + "%" + mInterfaceParams.index);
+ }
+ }
+
+ private void updateDelegatedAddresses(@NonNull final List<IaPrefixOption> valid) {
+ if (valid.isEmpty()) return;
+ for (IaPrefixOption ipo : valid) {
+ final IpPrefix prefix = ipo.getIpPrefix();
+ // The prefix with preferred/valid lifetime of 0 is considered as a valid prefix,
+ // it can be passed to IpClient from Dhcp6Client, however, client should stop using
+ // the global addresses derived from this prefix immediately.
+ if (ipo.withZeroLifetimes()) {
+ Log.d(TAG, "Delete IPv6 address derived from prefix " + prefix
+ + " with 0 preferred/valid lifetime");
+ deleteIpv6PrefixDelegationAddresses(prefix);
}
+ // Otherwise, configure IPv6 addresses derived from the delegated prefix(es) on
+ // the interface. We've checked that delegated prefix is valid upon receiving the
+ // response from DHCPv6 server, and the server may assign a prefix with length less
+ // than 64. So for SLAAC use case we always set the prefix length to 64 even if the
+ // delegated prefix length is less than 64.
+ final Inet6Address address = createInet6AddressFromEui64(prefix,
+ macAddressToEui64(mInterfaceParams.macAddr));
+ addInterfaceAddress(address, ipo);
+ }
+ }
+
+ private void removeExpiredDelegatedAddresses(@NonNull final List<IaPrefixOption> expired) {
+ if (expired.isEmpty()) return;
+ for (IaPrefixOption ipo : expired) {
+ final IpPrefix prefix = ipo.getIpPrefix();
+ Log.d(TAG, "Delete IPv6 address derived from expired prefix " + prefix);
+ deleteIpv6PrefixDelegationAddresses(prefix);
}
}
@@ -3216,15 +3299,14 @@ public class IpClient extends StateMachine {
case Dhcp6Client.CMD_DHCP6_RESULT:
switch(msg.arg1) {
case Dhcp6Client.DHCP6_PD_SUCCESS:
- mPrefixDelegation = (PrefixDelegation) msg.obj;
+ final List<IaPrefixOption> toBeUpdated = (List<IaPrefixOption>) msg.obj;
+ updateDelegatedAddresses(toBeUpdated);
handleLinkPropertiesUpdate(SEND_CALLBACKS);
break;
case Dhcp6Client.DHCP6_PD_PREFIX_EXPIRED:
- case Dhcp6Client.DHCP6_PD_PREFIX_CHANGED:
- case Dhcp6Client.DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED:
- clearIpv6PrefixDelegationAddresses();
- mPrefixDelegation = null;
+ final List<IaPrefixOption> toBeRemoved = (List<IaPrefixOption>) msg.obj;
+ removeExpiredDelegatedAddresses(toBeRemoved);
handleLinkPropertiesUpdate(SEND_CALLBACKS);
break;
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index 40a4bb65..e252a68b 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -23,7 +23,6 @@ import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -236,7 +235,6 @@ public class IpReachabilityMonitor {
private int mInterSolicitIntervalMs;
@NonNull
private final Callback mCallback;
- private final boolean mMulticastResolicitEnabled;
private final boolean mIgnoreIncompleteIpv6DnsServerEnabled;
private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled;
@@ -260,8 +258,6 @@ public class IpReachabilityMonitor {
mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
mCm = context.getSystemService(ConnectivityManager.class);
mDependencies = dependencies;
- mMulticastResolicitEnabled = dependencies.isFeatureNotChickenedOut(context,
- IP_REACHABILITY_MCAST_RESOLICIT_VERSION);
mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureNotChickenedOut(context,
IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION);
mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context,
@@ -274,10 +270,8 @@ public class IpReachabilityMonitor {
// In case the overylaid parameters specify an invalid configuration, set the parameters
// to the hardcoded defaults first, then set them to the values used in the steady state.
try {
- int numResolicits = mMulticastResolicitEnabled
- ? NUD_MCAST_RESOLICIT_NUM
- : INVALID_NUD_MCAST_RESOLICIT_NUM;
- setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
+ setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS,
+ NUD_MCAST_RESOLICIT_NUM);
} catch (Exception e) {
Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
}
@@ -414,8 +408,7 @@ public class IpReachabilityMonitor {
private void handleNeighborReachable(@Nullable final NeighborEvent prev,
@NonNull final NeighborEvent event) {
- if (mMulticastResolicitEnabled
- && hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
+ if (hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
// This implies device has confirmed the neighbor's reachability from
// other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
// address hasn't changed is required. If Mac address does change, then
@@ -581,8 +574,7 @@ public class IpReachabilityMonitor {
private long getProbeWakeLockDuration() {
final long gracePeriodMs = 500;
- final int numSolicits =
- mNumSolicits + (mMulticastResolicitEnabled ? NUD_MCAST_RESOLICIT_NUM : 0);
+ final int numSolicits = mNumSolicits + NUD_MCAST_RESOLICIT_NUM;
return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs;
}
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index 85da4003..37f10eae 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -197,13 +197,6 @@ public class NetworkStackUtils {
public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version";
/**
- * Experiment flag to send multicast NS from the global IPv6 GUA to the solicited-node
- * multicast address based on the default router's IPv6 link-local address, which helps
- * flush the first-hop routers' neighbor cache entry for the global IPv6 GUA.
- */
- public static final String IPCLIENT_MULTICAST_NS_VERSION = "ipclient_multicast_ns_version";
-
- /**
* Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for
* all assigned IPv4 and IPv6 GUAs after completing L2 roaming.
*/
@@ -219,13 +212,6 @@ public class NetworkStackUtils {
"ipclient_accept_ipv6_link_local_dns_version";
/**
- * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor,
- * set it to 3 by default.
- */
- public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION =
- "ip_reachability_mcast_resolicit_version";
-
- /**
* Experiment flag to attempt to ignore the on-link IPv6 DNS server which fails to respond to
* address resolution.
*/
@@ -250,12 +236,22 @@ public class NetworkStackUtils {
*/
public static final String APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version";
/**
+ * Experiment flag to enable the feature of polling counters in Apf.
+ */
+ public static final String APF_POLLING_COUNTERS_VERSION = "apf_polling_counters_version";
+ /**
* Experiment flag to enable the feature of ignoring any individual RA section with lifetime
* below accept_ra_min_lft sysctl.
*/
public static final String IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION =
"ipclient_ignore_low_ra_lifetime_version";
+ /**
+ * Feature flag to send private DNS resolution queries and probes on a background thread.
+ */
+ public static final String NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION =
+ "networkmonitor_async_privdns_resolution";
+
/**** BEGIN Feature Kill Switch Flags ****/
/**
@@ -272,12 +268,6 @@ public class NetworkStackUtils {
"apf_handle_light_doze_force_disable";
/**
- * Kill switch flag to disable the feature of polling counters in Apf.
- */
- public static final String APF_POLLING_COUNTERS_FORCE_DISABLE =
- "apf_polling_counters_force_disable";
-
- /**
* Kill switch flag to disable the feature of skipping Tcp socket info polling when light
* doze mode is enabled.
*/
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 9303f956..78a47ea8 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -23,6 +23,9 @@ import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
import static android.net.DnsResolver.FLAG_EMPTY;
+import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
+import static android.net.DnsResolver.TYPE_A;
+import static android.net.DnsResolver.TYPE_AAAA;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
@@ -122,6 +125,7 @@ import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -140,7 +144,9 @@ import android.telephony.CellSignalStrength;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import androidx.annotation.ArrayRes;
@@ -234,6 +240,10 @@ public class NetworkMonitor extends StateMachine {
@VisibleForTesting
static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT =
"captive_portal_dns_probe_timeout";
+ @VisibleForTesting
+ static final String CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS =
+ "async_privdns_probe_timeout_ms";
+ private static final int DEFAULT_PRIVDNS_PROBE_TIMEOUT_MS = 10_000;
private static final int SOCKET_TIMEOUT_MS = 10000;
private static final int PROBE_TIMEOUT_MS = 3000;
@@ -404,6 +414,32 @@ public class NetworkMonitor extends StateMachine {
*/
private static final int EVENT_RESOURCE_CONFIG_CHANGED = 25;
+ /**
+ * Message to self to notify that private DNS strict mode hostname resolution has finished.
+ *
+ * <p>arg2 = Last DNS rcode.
+ * obj = Pair&lt;List&lt;InetAddress&gt;, DnsCallback&gt;: query results and DnsCallback used.
+ */
+ private static final int CMD_STRICT_MODE_RESOLUTION_COMPLETED = 26;
+
+ /**
+ * Message to self to notify that the private DNS probe has finished.
+ *
+ * <p>arg2 = Last DNS rcode.
+ * obj = Pair&lt;List&lt;InetAddress&gt;, DnsCallback&gt;: query results and DnsCallback used.
+ */
+ private static final int CMD_PRIVATE_DNS_PROBE_COMPLETED = 27;
+
+ /**
+ * Message to self to notify that private DNS hostname resolution or probing has failed.
+ */
+ private static final int CMD_PRIVATE_DNS_EVALUATION_FAILED = 28;
+
+ /**
+ * Message to self to notify that a DNS query has timed out.
+ */
+ private static final int CMD_DNS_TIMEOUT = 29;
+
// Start mReevaluateDelayMs at this value and double.
@VisibleForTesting
static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
@@ -504,6 +540,10 @@ public class NetworkMonitor extends StateMachine {
private final State mEvaluatingState = new EvaluatingState();
private final State mCaptivePortalState = new CaptivePortalState();
private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState();
+ private final State mStartingPrivateDnsEvaluation = new StartingPrivateDnsEvaluation();
+ private final State mResolvingPrivateDnsState = new ResolvingPrivateDnsState();
+ private final State mProbingForPrivateDnsState = new ProbingForPrivateDnsState();
+
private final State mProbingState = new ProbingState();
private final State mWaitingForNextProbeState = new WaitingForNextProbeState();
private final State mEvaluatingBandwidthState = new EvaluatingBandwidthState();
@@ -544,6 +584,8 @@ public class NetworkMonitor extends StateMachine {
private final boolean mMetricsEnabled;
private final boolean mReevaluateWhenResumeEnabled;
+ private final boolean mAsyncPrivdnsResolutionEnabled;
+
@NonNull
private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance();
@@ -614,6 +656,9 @@ public class NetworkMonitor extends StateMachine {
addState(mWaitingForNextProbeState, mEvaluatingState);
addState(mCaptivePortalState, mMaybeNotifyState);
addState(mEvaluatingPrivateDnsState, mDefaultState);
+ addState(mStartingPrivateDnsEvaluation, mEvaluatingPrivateDnsState);
+ addState(mResolvingPrivateDnsState, mEvaluatingPrivateDnsState);
+ addState(mProbingForPrivateDnsState, mEvaluatingPrivateDnsState);
addState(mEvaluatingBandwidthState, mDefaultState);
addState(mValidatedState, mDefaultState);
setInitialState(mDefaultState);
@@ -632,6 +677,8 @@ public class NetworkMonitor extends StateMachine {
NetworkStackUtils.VALIDATION_METRICS_VERSION);
mReevaluateWhenResumeEnabled = deps.isFeatureEnabled(
context, NetworkStackUtils.REEVALUATE_WHEN_RESUME);
+ mAsyncPrivdnsResolutionEnabled = deps.isFeatureEnabled(context,
+ NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION);
mUseHttps = getUseHttpsValidation();
mCaptivePortalUserAgent = getCaptivePortalUserAgent();
mCaptivePortalFallbackSpecs =
@@ -1049,6 +1096,13 @@ public class NetworkMonitor extends StateMachine {
if (tst != null) {
tst.setOpportunisticMode(cfg.inOpportunisticMode());
}
+ if (mAsyncPrivdnsResolutionEnabled) {
+ // When using async privdns validation, reevaluate on any change of
+ // configuration (even if turning it off), as this will handle
+ // cancelling current attempts and transitioning to validated state.
+ removeMessages(CMD_EVALUATE_PRIVATE_DNS);
+ sendMessage(CMD_EVALUATE_PRIVATE_DNS);
+ }
break;
}
@@ -1599,13 +1653,28 @@ public class NetworkMonitor extends StateMachine {
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
- case CMD_EVALUATE_PRIVATE_DNS:
+ case CMD_EVALUATE_PRIVATE_DNS: {
+ if (mAsyncPrivdnsResolutionEnabled) {
+ // Cancel any previously scheduled retry attempt
+ removeMessages(CMD_EVALUATE_PRIVATE_DNS);
+
+ if (inStrictMode()) {
+ // Note this may happen even in the case where the current state is
+ // resolve or probe: private DNS evaluation would then restart.
+ transitionTo(mStartingPrivateDnsEvaluation);
+ } else {
+ mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS);
+ transitionToPrivateDnsEvaluationSuccessState();
+ }
+ break;
+ }
+
if (inStrictMode()) {
- if (!isStrictModeHostnameResolved()) {
+ if (!isStrictModeHostnameResolved(mPrivateDnsConfig)) {
resolveStrictModeHostname();
- if (isStrictModeHostnameResolved()) {
- notifyPrivateDnsConfigResolved();
+ if (isStrictModeHostnameResolved(mPrivateDnsConfig)) {
+ notifyPrivateDnsConfigResolved(mPrivateDnsConfig);
} else {
handlePrivateDnsEvaluationFailure();
// The private DNS probe fails-fast if the server hostname cannot
@@ -1630,23 +1699,24 @@ public class NetworkMonitor extends StateMachine {
handlePrivateDnsEvaluationFailure();
break;
}
- handlePrivateDnsEvaluationSuccess();
+ mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
+ true /* succeeded */);
} else {
mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS);
}
-
- if (needEvaluatingBandwidth()) {
- transitionTo(mEvaluatingBandwidthState);
- } else {
- // All good!
- transitionTo(mValidatedState);
- }
+ transitionToPrivateDnsEvaluationSuccessState();
break;
- case CMD_PRIVATE_DNS_SETTINGS_CHANGED:
+ }
+ case CMD_PRIVATE_DNS_SETTINGS_CHANGED: {
// When settings change the reevaluation timer must be reset.
mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
// Let the message bubble up and be handled by parent states as usual.
return NOT_HANDLED;
+ }
+ // Only used with mAsyncPrivdnsResolutionEnabled
+ case CMD_PRIVATE_DNS_EVALUATION_FAILED: {
+ reschedulePrivateDnsEvaluation();
+ }
default:
return NOT_HANDLED;
}
@@ -1657,12 +1727,6 @@ public class NetworkMonitor extends StateMachine {
return !TextUtils.isEmpty(mPrivateDnsProviderHostname);
}
- private boolean isStrictModeHostnameResolved() {
- return (mPrivateDnsConfig != null)
- && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname)
- && (mPrivateDnsConfig.ips.length > 0);
- }
-
private void resolveStrictModeHostname() {
try {
// Do a blocking DNS resolution using the network-assigned nameservers.
@@ -1675,24 +1739,15 @@ public class NetworkMonitor extends StateMachine {
}
}
- private void notifyPrivateDnsConfigResolved() {
- try {
- mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending private DNS config resolved notification", e);
- }
- }
-
- private void handlePrivateDnsEvaluationSuccess() {
- mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
- true /* succeeded */);
- }
-
private void handlePrivateDnsEvaluationFailure() {
mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
false /* succeeded */);
mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
null /* redirectUrl */);
+ reschedulePrivateDnsEvaluation();
+ }
+
+ private void reschedulePrivateDnsEvaluation() {
// Queue up a re-evaluation with backoff.
//
// TODO: Consider abandoning this state after a few attempts and
@@ -1730,6 +1785,285 @@ public class NetworkMonitor extends StateMachine {
}
}
+ private void transitionToPrivateDnsEvaluationSuccessState() {
+ if (needEvaluatingBandwidth()) {
+ transitionTo(mEvaluatingBandwidthState);
+ } else {
+ // All good!
+ transitionTo(mValidatedState);
+ }
+ }
+
+ private class StartingPrivateDnsEvaluation extends State {
+ @Override
+ public void enter() {
+ transitionTo(mResolvingPrivateDnsState);
+ }
+ }
+
+ private class DnsCallback implements DnsResolver.Callback<List<InetAddress>> {
+ private final int mReplyMessage;
+ final CancellationSignal mCancellationSignal;
+ final boolean mHighPriorityResults;
+
+ DnsCallback(int replyMessage, boolean highPriorityResults) {
+ mReplyMessage = replyMessage;
+ mCancellationSignal = new CancellationSignal();
+ mHighPriorityResults = highPriorityResults;
+ }
+
+ @Override
+ public void onAnswer(List<InetAddress> answer, int rcode) {
+ sendMessage(mReplyMessage, 0, rcode, new Pair<>(answer, this));
+ }
+
+ @Override
+ public void onError(DnsResolver.DnsException error) {
+ sendMessage(mReplyMessage, 0, error.code, new Pair<>(null, this));
+ }
+ }
+
+ /**
+ * Base class for a state that is sending a DNS query, cancelled if the state is exited.
+ */
+ private abstract class DnsQueryState extends State {
+ private static final int ERROR_TIMEOUT = -1;
+ private final int mCompletedCommand;
+ private final ArraySet<DnsCallback> mPendingQueries = new ArraySet<>(2);
+ private final List<InetAddress> mResults = new ArrayList<>();
+ private String mQueryName;
+ private long mStartTime;
+
+ private DnsQueryState(int completedCommand) {
+ mCompletedCommand = completedCommand;
+ }
+
+ @Override
+ public void enter() {
+ mPendingQueries.clear();
+ mResults.clear();
+ mStartTime = SystemClock.elapsedRealtimeNanos();
+
+ mQueryName = getQueryName();
+ if (TextUtils.isEmpty(mQueryName)) {
+ // No query necessary (in particular not in strict mode): skip DNS query states
+ mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS);
+ transitionToPrivateDnsEvaluationSuccessState();
+ return;
+ }
+
+ final DnsResolver resolver = mDependencies.getDnsResolver();
+ mPendingQueries.addAll(sendQueries(mQueryName, resolver));
+ sendMessageDelayed(CMD_DNS_TIMEOUT, getTimeoutMs());
+ }
+
+ @Override
+ public void exit() {
+ removeMessages(CMD_DNS_TIMEOUT);
+ cancelAllQueries();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what == mCompletedCommand) {
+ final Pair<List<InetAddress>, DnsCallback> result =
+ (Pair<List<InetAddress>, DnsCallback>) msg.obj;
+ if (!mPendingQueries.remove(result.second)) {
+ // Ignore previous queries if the state was exited and re-entered. This state
+ // calls cancelAllQueries on exit, but this may still happen if results were
+ // already posted when the querier processed the cancel request.
+ return HANDLED;
+ }
+
+ if (result.first != null) {
+ if (result.second.mHighPriorityResults) {
+ mResults.addAll(0, result.first);
+ } else {
+ mResults.addAll(result.first);
+ }
+ }
+
+ if (mPendingQueries.isEmpty()) {
+ removeMessages(CMD_DNS_TIMEOUT);
+ final long time = SystemClock.elapsedRealtimeNanos() - mStartTime;
+ onQueryDone(mQueryName, mResults, msg.arg2 /* lastRCode */, time);
+ }
+ return HANDLED;
+ } else if (msg.what == CMD_DNS_TIMEOUT) {
+ cancelAllQueries();
+ // If some queries were successful, onQueryDone will still proceed, even if
+ // lastRCode is not a success code.
+ onQueryDone(mQueryName, mResults, ERROR_TIMEOUT /* lastRCode */,
+ SystemClock.elapsedRealtimeNanos() - mStartTime);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ private void cancelAllQueries() {
+ for (int i = 0; i < mPendingQueries.size(); i++) {
+ mPendingQueries.valueAt(i).mCancellationSignal.cancel();
+ }
+ mPendingQueries.clear();
+ }
+
+ abstract void onQueryDone(@NonNull String queryName, @NonNull List<InetAddress> answer,
+ int lastRCode, long elapsedNanos);
+
+ @NonNull
+ abstract String getQueryName();
+
+ abstract List<DnsCallback> sendQueries(@NonNull String queryName,
+ @NonNull DnsResolver resolver);
+
+ abstract long getTimeoutMs();
+ }
+
+ private class ResolvingPrivateDnsState extends DnsQueryState {
+ private ResolvingPrivateDnsState() {
+ super(CMD_STRICT_MODE_RESOLUTION_COMPLETED);
+ }
+
+ @Override
+ List<DnsCallback> sendQueries(@NonNull String queryName, @NonNull DnsResolver resolver) {
+ // Follow legacy behavior that sent AAAA and A queries synchronously in sequence: AAAA
+ // is marked as highPriorityResults, so they are placed first in the resulting list.
+ final DnsCallback v6Cb = new DnsCallback(CMD_STRICT_MODE_RESOLUTION_COMPLETED,
+ true /* highPriorityResults */);
+ final DnsCallback v4Cb = new DnsCallback(CMD_STRICT_MODE_RESOLUTION_COMPLETED,
+ false /* highPriorityResults */);
+
+ resolver.query(mCleartextDnsNetwork, queryName, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ Runnable::run, v6Cb.mCancellationSignal, v6Cb);
+ resolver.query(mCleartextDnsNetwork, queryName, TYPE_A, FLAG_NO_CACHE_LOOKUP,
+ Runnable::run, v4Cb.mCancellationSignal, v4Cb);
+
+ return List.of(v6Cb, v4Cb);
+ }
+
+ @Override
+ void onQueryDone(@NonNull String queryName, @NonNull List<InetAddress> answer,
+ int lastRCode, long elapsedNanos) {
+ if (!Objects.equals(queryName, mPrivateDnsProviderHostname)) {
+ validationLog("Ignoring stale private DNS resolve answers for " + queryName
+ + " (now \"" + mPrivateDnsProviderHostname + "\"): " + answer);
+ // This may happen if mPrivateDnsProviderHostname was changed, in which case
+ // reevaluation must have been queued (CMD_EVALUATE_PRIVATE_DNS), but results for
+ // the first evaluation are received before the reevaluation command gets processed.
+ // Just ignore the results and wait for reevaluation to be processed.
+ // More generally, reevaluation is scheduled every time the hostname changes, so
+ // IP addresses matching the hostname are eventually received, but intermediate
+ // results should be ignored to avoid reporting a PrivateDnsConfig with IP addresses
+ // that don't match mPrivateDnsProviderHostname.
+ return;
+ }
+
+ if (!answer.isEmpty()) {
+ final InetAddress[] ips = answer.toArray(new InetAddress[0]);
+ final PrivateDnsConfig config =
+ new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
+ notifyPrivateDnsConfigResolved(config);
+
+ validationLog("Strict mode hostname resolution " + elapsedNanos + "ns OK "
+ + answer + " for " + mPrivateDnsProviderHostname);
+ transitionTo(mProbingForPrivateDnsState);
+ } else {
+ mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
+ false /* succeeded */);
+ mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+ null /* redirectUrl */);
+
+ validationLog("Strict mode hostname resolution " + elapsedNanos + "ns FAIL "
+ + "lastRCode " + lastRCode + " for " + mPrivateDnsProviderHostname);
+ sendMessage(CMD_PRIVATE_DNS_EVALUATION_FAILED);
+
+ // The private DNS probe fails-fast if the server hostname cannot
+ // be resolved. Record it as a failure with zero latency.
+ recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */,
+ ProbeResult.PR_FAILURE, null /* capportData */);
+ }
+ }
+
+ @NonNull
+ @Override
+ String getQueryName() {
+ return mPrivateDnsProviderHostname;
+ }
+
+ @Override
+ long getTimeoutMs() {
+ return getDnsProbeTimeout();
+ }
+ }
+
+ private class ProbingForPrivateDnsState extends DnsQueryState {
+ private ProbingForPrivateDnsState() {
+ super(CMD_PRIVATE_DNS_PROBE_COMPLETED);
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ }
+
+ @Override
+ List<DnsCallback> sendQueries(@NonNull String queryName, @NonNull DnsResolver resolver) {
+ final DnsCallback cb = new DnsCallback(CMD_PRIVATE_DNS_PROBE_COMPLETED,
+ false /* highPriorityResults */);
+ resolver.query(mNetwork, queryName, FLAG_EMPTY, Runnable::run, cb.mCancellationSignal,
+ cb);
+ return Collections.singletonList(cb);
+ }
+
+ @Override
+ void onQueryDone(@NonNull String queryName, @NonNull List<InetAddress> answer,
+ int lastRCode, long elapsedNanos) {
+ final boolean success = !answer.isEmpty();
+ recordProbeEventMetrics(ProbeType.PT_PRIVDNS, elapsedNanos,
+ success ? ProbeResult.PR_SUCCESS :
+ ProbeResult.PR_FAILURE, null /* capportData */);
+ logValidationProbe(elapsedNanos, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
+
+ final String strIps = Objects.toString(answer);
+ validationLog(PROBE_PRIVDNS, queryName,
+ String.format("%dus: %s", elapsedNanos / 1000, strIps));
+
+ mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success);
+ if (success) {
+ transitionToPrivateDnsEvaluationSuccessState();
+ } else {
+ mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+ null /* redirectUrl */);
+ sendMessage(CMD_PRIVATE_DNS_EVALUATION_FAILED);
+ }
+ }
+
+ @Override
+ long getTimeoutMs() {
+ return getAsyncPrivateDnsProbeTimeout();
+ }
+
+ @NonNull
+ @Override
+ String getQueryName() {
+ return UUID.randomUUID().toString().substring(0, 8) + PRIVATE_DNS_PROBE_HOST_SUFFIX;
+ }
+ }
+
+ private boolean isStrictModeHostnameResolved(PrivateDnsConfig config) {
+ return (config != null)
+ && config.hostname.equals(mPrivateDnsProviderHostname)
+ && (config.ips.length > 0);
+ }
+
+ private void notifyPrivateDnsConfigResolved(@NonNull PrivateDnsConfig config) {
+ try {
+ mCallback.notifyPrivateDnsConfigResolved(config.toParcel());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending private DNS config resolved notification", e);
+ }
+ }
+
private class ProbingState extends State {
private Thread mThread;
@@ -2186,6 +2520,11 @@ public class NetworkMonitor extends StateMachine {
CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT, DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT);
}
+ private int getAsyncPrivateDnsProbeTimeout() {
+ return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY,
+ CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS, DEFAULT_PRIVDNS_PROBE_TIMEOUT_MS);
+ }
+
/**
* Gets an integer setting from resources or device config
*
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 8300c73f..ec058d7a 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -68,6 +68,9 @@ android_library {
static_libs: [
"NetworkStackApiStableLib",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Network stack integration tests.
@@ -84,6 +87,9 @@ android_test {
jarjar_rules: ":NetworkStackJarJarRules",
host_required: ["net-tests-utils-host-common"],
test_config_template: "AndroidTestTemplate_Integration.xml",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Network stack next integration tests.
@@ -107,6 +113,9 @@ android_test {
jarjar_rules: ":NetworkStackJarJarRules",
host_required: ["net-tests-utils-host-common"],
test_config_template: "AndroidTestTemplate_Integration.xml",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Network stack integration root tests.
@@ -124,12 +133,18 @@ android_test {
"NetworkStackApiStableLib",
],
platform_apis: true,
- test_suites: ["general-tests", "mts-networking"],
+ test_suites: [
+ "general-tests",
+ "mts-networking",
+ ],
compile_multilib: "both",
manifest: "AndroidManifest_root.xml",
jarjar_rules: ":NetworkStackJarJarRules",
host_required: ["net-tests-utils-host-common"],
test_config_template: "AndroidTestTemplate_Integration.xml",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Special version of the network stack tests that includes all tests necessary for code coverage
@@ -138,7 +153,10 @@ android_test {
name: "NetworkStackCoverageTests",
certificate: "networkstack",
platform_apis: true,
- test_suites: ["device-tests", "mts-networking"],
+ test_suites: [
+ "device-tests",
+ "mts-networking",
+ ],
test_config: "AndroidTest_Coverage.xml",
defaults: [
"NetworkStackReleaseTargetSdk",
@@ -154,4 +172,7 @@ android_test {
compile_multilib: "both",
manifest: "AndroidManifest_coverage.xml",
jarjar_rules: ":NetworkStackJarJarRules",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index c733f9c9..2f1f7d16 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -312,6 +312,7 @@ public abstract class IpClientIntegrationTestCommon {
protected static final long TEST_TIMEOUT_MS = 2_000L;
private static final long TEST_WAIT_ENOBUFS_TIMEOUT_MS = 30_000L;
+ private static final long TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS = 15_000L;
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
@@ -779,6 +780,8 @@ public abstract class IpClientIntegrationTestCommon {
enableRealAlarm("DhcpClient." + mIfaceName + ".KICK");
// Enable alarm for IPv6 autoconf via SLAAC in IpClient.
enableRealAlarm("IpClient." + mIfaceName + ".EVENT_IPV6_AUTOCONF_TIMEOUT");
+ // Enable packet retransmit alarm in Dhcp6Client.
+ enableRealAlarm("Dhcp6Client." + mIfaceName + ".KICK");
}
mIIpClient = makeIIpClient(mIfaceName, mCb);
@@ -4137,12 +4140,6 @@ public abstract class IpClientIntegrationTestCommon {
return ns;
}
- // Override this function with disabled experiment flag by default, in order not to
- // affect those tests which are just related to basic IpReachabilityMonitor infra.
- private void prepareIpReachabilityMonitorTest() throws Exception {
- prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */);
- }
-
private void assertNotifyNeighborLost(Inet6Address targetIp, NudEventType eventType)
throws Exception {
// For root test suite, rely on the IIpClient aidl interface version constant defined in
@@ -4175,8 +4172,7 @@ public abstract class IpClientIntegrationTestCommon {
verify(mCb, never()).onReachabilityLost(any());
}
- private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled)
- throws Exception {
+ private void prepareIpReachabilityMonitorTest() throws Exception {
final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -4185,8 +4181,6 @@ public abstract class IpClientIntegrationTestCommon {
.withDisplayName(TEST_DEFAULT_SSID)
.withoutIPv4()
.build();
- setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
- isMulticastResolicitEnabled);
startIpClientProvisioning(config);
verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true);
doIpv6OnlyProvisioning();
@@ -4200,11 +4194,15 @@ public abstract class IpClientIntegrationTestCommon {
final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource();
- assertEquals(expectedNudSolicitNum, nsList.size());
- for (NeighborSolicitation ns : nsList) {
+ int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM;
+ assertEquals(expectedSize, nsList.size());
+ for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) {
assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
}
+ for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) {
+ assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
+ }
}
@Test
@@ -4239,43 +4237,10 @@ public abstract class IpClientIntegrationTestCommon {
assertNeverNotifyNeighborLost();
}
- private void runIpReachabilityMonitorMcastResolicitProbeFailedTest() throws Exception {
- prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
-
- final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
- final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource();
- int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM;
- assertEquals(expectedSize, nsList.size());
- for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) {
- assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
- ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
- }
- for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) {
- assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
- }
- }
-
- @Test
- public void testIpReachabilityMonitor_mcastResolicitProbeFailed() throws Exception {
- runIpReachabilityMonitorMcastResolicitProbeFailedTest();
- assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */,
- NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL);
- }
-
- @Test @SignatureRequiredTest(reason = "requires mock callback object")
- public void testIpReachabilityMonitor_mcastResolicitProbeFailed_legacyCallback()
- throws Exception {
- when(mCb.getInterfaceVersion()).thenReturn(12 /* assign an older interface aidl version */);
-
- runIpReachabilityMonitorMcastResolicitProbeFailedTest();
- verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityLost(any());
- verify(mCb, never()).onReachabilityFailure(any());
- }
-
@Test
public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithSameLinkLayerAddress()
throws Exception {
- prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+ prepareIpReachabilityMonitorTest();
final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -4292,7 +4257,7 @@ public abstract class IpClientIntegrationTestCommon {
@Test
public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithDiffLinkLayerAddress()
throws Exception {
- prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+ prepareIpReachabilityMonitorTest();
final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -4664,9 +4629,6 @@ public abstract class IpClientIntegrationTestCommon {
.withoutIPv4()
.build();
- setFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION,
- true /* isUnsolicitedNsEnabled */);
- assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION));
startIpClientProvisioning(config);
doIpv6OnlyProvisioning();
@@ -5216,9 +5178,7 @@ public abstract class IpClientIntegrationTestCommon {
x -> x.isIpv6Provisioned()
&& hasIpv6AddressPrefixedWith(x, prefix)
&& hasIpv6AddressPrefixedWith(x, prefix1)
- // TODO: comment this line to make the test passed, remove the comment later
- // once IpClient supports multi-prefixes.
- // && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
+ && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
&& hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE)
// IPv6 link-local, four global delegated IPv6 addresses
&& x.getLinkAddresses().size() == 5
@@ -5280,14 +5240,15 @@ public abstract class IpClientIntegrationTestCommon {
mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
(Inet6Address) mClientIpAddress, false /* rapidCommit */));
verify(mCb, never()).onProvisioningFailure(any());
+ // IPv6 addresses derived from prefix with 0 preferred/valid lifetime should be deleted.
verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat(
x -> x.isIpv6Provisioned()
- && hasIpv6AddressPrefixedWith(x, prefix)
+ && !hasIpv6AddressPrefixedWith(x, prefix)
&& hasIpv6AddressPrefixedWith(x, prefix1)
- && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
+ && !hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
&& hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE)
- // IPv6 link-local, four global delegated IPv6 addresses
- && x.getLinkAddresses().size() == 5
+ // IPv6 link-local, two global delegated IPv6 addresses with prefix1
+ && x.getLinkAddresses().size() == 3
));
handler.post(() -> renewAlarm.onAlarm());
@@ -5296,7 +5257,8 @@ public abstract class IpClientIntegrationTestCommon {
packet = getNextDhcp6Packet();
assertTrue(packet instanceof Dhcp6RenewPacket);
final List<IaPrefixOption> renewIpos = packet.getPrefixDelegation().ipos;
- assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64
+ assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64 with 0
+ // preferred/valid lifetime
assertEquals(prefix1, renewIpos.get(0).getIpPrefix());
}
@@ -5317,7 +5279,7 @@ public abstract class IpClientIntegrationTestCommon {
clearInvocations(mCb);
- // Reply with the requested prefix with preferred/valid lifetime of 0.
+ // Reply with the requested prefix with the same t1/t2/lifetime.
final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */,
3600 /* valid */);
@@ -5339,6 +5301,220 @@ public abstract class IpClientIntegrationTestCommon {
}
@Test
+ public void testDhcp6Pd_multipleIaPrefixOptions() throws Exception {
+ final InOrder inOrder = inOrder(mCb);
+ final IpPrefix prefix1 = new IpPrefix("2001:db8:1::/64");
+ final IpPrefix prefix2 = new IpPrefix("2400:db8:100::/64");
+ final IpPrefix prefix3 = new IpPrefix("fd7c:9df8:7f39:dc89::/64");
+ final IaPrefixOption ipo1 = buildIaPrefixOption(prefix1, 4500 /* preferred */,
+ 7200 /* valid */);
+ final IaPrefixOption ipo2 = buildIaPrefixOption(prefix2, 5600 /* preferred */,
+ 6000 /* valid */);
+ final IaPrefixOption ipo3 = buildIaPrefixOption(prefix3, 7200 /* preferred */,
+ 14400 /* valid */);
+ prepareDhcp6PdTest();
+ handleDhcp6Packets(Arrays.asList(ipo1, ipo2, ipo3), 3600 /* t1 */, 4500 /* t2 */,
+ true /* shouldReplyRapidCommit */);
+
+ final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+ verifyWithTimeout(inOrder, mCb).onProvisioningSuccess(captor.capture());
+ LinkProperties lp = captor.getValue();
+
+ // Sometimes privacy address or route may appear later along with onLinkPropertiesChange
+ // callback, in this case we wait a bit longer to see all of these properties appeared and
+ // then verify if they are what we are looking for.
+ if (lp.getLinkAddresses().size() < 5 || lp.getRoutes().size() < 4) {
+ final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+ verifyWithTimeout(inOrder, mCb).onLinkPropertiesChange(argThat(x -> {
+ if (!x.isIpv6Provisioned()) return false;
+ if (x.getLinkAddresses().size() != 5) return false;
+ if (x.getRoutes().size() != 4) return false;
+ lpFuture.complete(x);
+ return true;
+ }));
+ lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+ assertNotNull(lp);
+ assertTrue(hasIpv6AddressPrefixedWith(lp, prefix1));
+ assertTrue(hasIpv6AddressPrefixedWith(lp, prefix2));
+ assertFalse(hasIpv6AddressPrefixedWith(lp, prefix3));
+ assertTrue(hasRouteTo(lp, prefix1.toString(), RTN_UNREACHABLE));
+ assertTrue(hasRouteTo(lp, prefix2.toString(), RTN_UNREACHABLE));
+ assertFalse(hasRouteTo(lp, prefix3.toString(), RTN_UNREACHABLE));
+ }
+
+ private void runDhcp6PacketWithNoPrefixAvailStatusCodeTest(boolean shouldReplyWithAdvertise)
+ throws Exception {
+ prepareDhcp6PdTest();
+ Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6SolicitPacket);
+
+ final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */,
+ new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+ final ByteBuffer iapd = pd.build();
+ if (shouldReplyWithAdvertise) {
+ mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress));
+ } else {
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, true /* rapidCommit */));
+ }
+
+ // Check if client will ignore Advertise or Reply for Rapid Commit Solicit and
+ // retransmit Solicit.
+ packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6SolicitPacket);
+ }
+
+ @Test
+ public void testDhcp6AdvertiseWithNoPrefixAvailStatusCode() throws Exception {
+ // Advertise
+ runDhcp6PacketWithNoPrefixAvailStatusCodeTest(true /* shouldReplyWithAdvertise */);
+ }
+
+ @Test
+ public void testDhcp6ReplyForRapidCommitSolicitWithNoPrefixAvailStatusCode() throws Exception {
+ // Reply
+ runDhcp6PacketWithNoPrefixAvailStatusCodeTest(false /* shouldReplyWithAdvertise */);
+ }
+
+ @Test
+ public void testDhcp6ReplyForRequestWithNoPrefixAvailStatusCode() throws Exception {
+ prepareDhcp6PdTest();
+ Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6SolicitPacket);
+
+ final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+ final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */,
+ 7200 /* valid */);
+ PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */,
+ 2000 /* t2 */, Arrays.asList(ipo));
+ ByteBuffer iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress));
+
+ packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6RequestPacket);
+
+ // Reply for Request with NoPrefixAvail status code. Not sure if this is reasonable in
+ // practice, but Server can do everything it wants.
+ pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */,
+ new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+ iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+
+ // Check if client will ignore Reply for Request with NoPrefixAvail status code, and
+ // rollback to SolicitState.
+ packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6SolicitPacket);
+ }
+
+ @Test
+ @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms")
+ public void testDhcp6ReplyForRenewWithNoPrefixAvailStatusCode() throws Exception {
+ prepareDhcp6PdRenewTest();
+
+ final InOrder inOrder = inOrder(mAlarm);
+ final Handler handler = mDependencies.mDhcp6Client.getHandler();
+ final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler);
+
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ Dhcp6Packet packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+
+ // Reply with normal IA_PD.
+ final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+ final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */,
+ 7200 /* valid */);
+ PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */,
+ 2000 /* t2 */, Arrays.asList(ipo));
+ ByteBuffer iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ // Trigger another Renew message.
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+
+ // Reply for Renew with NoPrefixAvail status code, check if client will retransmit the
+ // Renew message.
+ pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, 4500 /* t2 */,
+ new ArrayList<IaPrefixOption>(0) /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+ iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+
+ packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS);
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+ }
+
+ @Test
+ @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms")
+ public void testDhcp6ReplyForRebindWithNoPrefixAvailStatusCode() throws Exception {
+ prepareDhcp6PdRenewTest();
+
+ final InOrder inOrder = inOrder(mAlarm);
+ final Handler handler = mDependencies.mDhcp6Client.getHandler();
+ final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler);
+ final OnAlarmListener rebindAlarm = expectAlarmSet(inOrder, "REBIND", 4500, handler);
+
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ Dhcp6Packet packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+
+ handler.post(() -> rebindAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RebindPacket);
+
+ // Reply with normal IA_PD.
+ final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+ final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */,
+ 7200 /* valid */);
+ PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */,
+ 2000 /* t2 */, Arrays.asList(ipo));
+ ByteBuffer iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ // Trigger another Rebind message.
+ handler.post(() -> renewAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RenewPacket);
+
+ handler.post(() -> rebindAlarm.onAlarm());
+ HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+ packet = getNextDhcp6Packet();
+ assertTrue(packet instanceof Dhcp6RebindPacket);
+
+ // Reply for Rebind with NoPrefixAvail status code, check if client will retransmit the
+ // Rebind message.
+ pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */,
+ 4500 /* t2 */, new ArrayList<IaPrefixOption>(0) /* ipos */,
+ Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+ iapd = pd.build();
+ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+ (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+
+ packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS);
+ assertTrue(packet instanceof Dhcp6RebindPacket);
+ }
+
+ @Test
@SignatureRequiredTest(reason = "InterfaceParams.getByName requires CAP_NET_ADMIN")
public void testSendRtmDelAddressMethod() throws Exception {
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 464e4a17..ea297146 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -21,7 +21,10 @@ package {
java_defaults {
name: "NetworkStackTestsDefaults",
platform_apis: true,
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
"androidx.test.ext.junit",
@@ -39,7 +42,7 @@ java_defaults {
],
defaults: [
"framework-connectivity-test-defaults",
- "libnetworkstackutilsjni_deps"
+ "libnetworkstackutilsjni_deps",
],
jni_libs: [
// For mockito extended
@@ -66,6 +69,9 @@ android_test {
static_libs: ["NetworkStackApiCurrentLib"],
compile_multilib: "both", // Workaround for b/147785146 for mainline-presubmit
jarjar_rules: ":NetworkStackJarJarRules",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -76,18 +82,24 @@ android_library {
min_sdk_version: "30",
defaults: ["NetworkStackTestsDefaults"],
static_libs: ["NetworkStackApiStableLib"],
- lint: { test: true },
+ lint: {
+ test: true,
+ baseline_filename: "lint-baseline.xml",
+ },
visibility: [
"//packages/modules/NetworkStack/tests/integration",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
- ]
+ ],
}
android_test {
name: "NetworkStackTests",
min_sdk_version: "30",
- test_suites: ["general-tests", "mts"],
+ test_suites: [
+ "general-tests",
+ "mts",
+ ],
defaults: [
"NetworkStackTestsDefaults",
"connectivity-mainline-presubmit-java-defaults",
@@ -95,6 +107,9 @@ android_test {
static_libs: ["NetworkStackApiStableLib"],
compile_multilib: "both",
jarjar_rules: ":NetworkStackJarJarRules",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Additional dependencies of libnetworkstackutilsjni that are not provided by the system when
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 0b61e04d..4e1187b1 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -16,6 +16,7 @@
package android.net.apf;
+import static android.net.apf.ApfGenerator.APF_VERSION_4;
import static android.net.apf.ApfGenerator.Register.R0;
import static android.net.apf.ApfGenerator.Register.R1;
import static android.net.apf.ApfJniUtils.compareBpfApf;
@@ -631,7 +632,7 @@ public class ApfTest {
// Test jump if bytes not equal.
gen = new ApfGenerator(MIN_APF_VERSION);
gen.addLoadImmediate(R0, 1);
- gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL);
+ gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL);
program = gen.generate();
assertEquals(6, program.length);
assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
@@ -643,20 +644,20 @@ public class ApfTest {
assertDrop(program, new byte[MIN_PKT_SIZE], 0);
gen = new ApfGenerator(MIN_APF_VERSION);
gen.addLoadImmediate(R0, 1);
- gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL);
+ gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.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 ApfGenerator(MIN_APF_VERSION);
- gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL);
+ gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL);
assertDrop(gen, packet123, 0);
gen = new ApfGenerator(MIN_APF_VERSION);
gen.addLoadImmediate(R0, 1);
- gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
+ gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, gen.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 ApfGenerator(MIN_APF_VERSION);
gen.addLoadImmediate(R0, 1);
- gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
+ gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, gen.DROP_LABEL);
assertPass(gen, packet12345, 0);
}
@@ -747,23 +748,23 @@ public class ApfTest {
ApfGenerator gen;
// Load data with no offset: lddw R0, [0 + r1]
- gen = new ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadData(R0, 0);
assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate());
// Store data with 8bit negative offset: lddw r0, [-42 + r1]
- gen = new ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
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 ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
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 ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadData(R1, 0xDEADBEEF);
assertProgramEquals(
new byte[]{LDDW_OP | SIZE32 | R1_REG,
@@ -781,12 +782,12 @@ public class ApfTest {
byte[] expected_data = data.clone();
// No memory access instructions: should leave the data segment untouched.
- ApfGenerator gen = new ApfGenerator(3);
+ ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
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 ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R0, 0x87654321);
gen.addLoadImmediate(R1, -5);
gen.addStoreData(R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16)
@@ -803,7 +804,7 @@ public class ApfTest {
@Test
public void testApfDataRead() throws IllegalInstructionException, Exception {
// Program that DROPs if address 10 (-6) contains 0x87654321.
- ApfGenerator gen = new ApfGenerator(3);
+ ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R1, 1000);
gen.addLoadData(R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16)
gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
@@ -832,7 +833,7 @@ public class ApfTest {
*/
@Test
public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
- ApfGenerator gen = new ApfGenerator(3);
+ ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R1, -22);
gen.addLoadData(R0, 0); // Load from address 32 -22 + 0 = 10
gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733
@@ -859,35 +860,35 @@ public class ApfTest {
byte[] expected_data = data;
// Program that DROPs unconditionally. This is our the baseline.
- ApfGenerator gen = new ApfGenerator(3);
+ ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R0, 3);
gen.addLoadData(R1, 7);
gen.addJump(gen.DROP_LABEL);
assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
// Same program as before, but this time we're trying to load past the end of the data.
- gen = new ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R0, 20);
gen.addLoadData(R1, 15); // 20 + 15 > 32
gen.addJump(gen.DROP_LABEL); // Not reached.
assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
// Subtracting an immediate should work...
- gen = new ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R0, 20);
gen.addLoadData(R1, -4);
gen.addJump(gen.DROP_LABEL);
assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
// ...and underflowing simply wraps around to the end of the buffer...
- gen = new ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R0, 20);
gen.addLoadData(R1, -30);
gen.addJump(gen.DROP_LABEL);
assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
// ...but doesn't allow accesses before the start of the buffer
- gen = new ApfGenerator(3);
+ gen = new ApfGenerator(APF_VERSION_4);
gen.addLoadImmediate(R0, 20);
gen.addLoadData(R1, -1000);
gen.addJump(gen.DROP_LABEL); // Not reached.
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt
index 1b74c3ee..162feef2 100644
--- a/tests/unit/src/android/net/apf/ApfV5Test.kt
+++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -16,6 +16,8 @@
package android.net.apf
import android.net.apf.ApfGenerator.IllegalInstructionException
+import android.net.apf.ApfGenerator.Register.R0
+import android.net.apf.ApfGenerator.Register.R1
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import java.lang.IllegalArgumentException
@@ -39,13 +41,130 @@ class ApfV5Test {
assertFailsWith<IllegalInstructionException> { gen.addCountAndPass(1000) }
assertFailsWith<IllegalInstructionException> { gen.addTransmit() }
assertFailsWith<IllegalInstructionException> { gen.addDiscard() }
+ assertFailsWith<IllegalInstructionException> { gen.addAllocateR0() }
+ assertFailsWith<IllegalInstructionException> { gen.addAllocate(100) }
+ assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU8(100) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU16(100) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU32(100) }
+ assertFailsWith<IllegalInstructionException> { gen.addPacketCopy(100, 100) }
+ assertFailsWith<IllegalInstructionException> { gen.addDataCopy(100, 100) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R0) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R0) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R0) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R1) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R1) }
+ assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R1) }
+ assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0LenR1() }
+ assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0LenR1() }
+ assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0(10) }
+ assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0(10) }
+ assertFailsWith<IllegalInstructionException> {
+ gen.addJumpIfBytesAtR0Equal(byteArrayOf('A'.code.toByte()), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
}
@Test
- fun testApfInstructionArgumentCheck() {
+ fun testDataInstructionMustComeFirst() {
var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
- assertFailsWith<IllegalArgumentException> { gen.addCountAndPass(0) }
- assertFailsWith<IllegalArgumentException> { gen.addCountAndDrop(0) }
+ gen.addAllocateR0()
+ assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) }
+ }
+
+ @Test
+ fun testApfInstructionEncodingSizeCheck() {
+ var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) }
+ assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) }
+ assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) }
+ assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(-1, 1) }
+ assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, 256) }
+ assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, 256) }
+ assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, -1) }
+ assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, -1) }
+ assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(256) }
+ assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(256) }
+ assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(-1) }
+ assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(-1) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte(), 0, 0), 256, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(1, 'a'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(1, '.'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(0, 0), 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte()), 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0),
+ 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0),
+ 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()),
+ 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte(), 0, 0), 256, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(1, 'a'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(1, '.'.code.toByte(), 0, 0), 0x0c, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(0, 0), 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte()), 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0),
+ 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0),
+ 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()),
+ 0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()),
+ ApfGenerator.DROP_LABEL) }
}
@Test
@@ -81,10 +200,18 @@ class ApfV5Test {
0x03, 0xe8.toByte()), program)
gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
- gen.addAlloc(ApfGenerator.Register.R0)
+ gen.addAllocateR0()
+ gen.addAllocate(1500)
program = gen.generate()
- assertContentEquals(byteArrayOf(encodeInstruction(21, 1, 0), 36), program)
- assertContentEquals(arrayOf(" 0: alloc r0"), ApfJniUtils.disassembleApf(program))
+ // encoding ALLOC opcode: opcode=21(EXT opcode number), imm=36(TRANS opcode number).
+ // R=0 means length stored in R0. R=1 means the length stored in imm1.
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(opcode = 21, immLength = 1, register = 0), 36,
+ encodeInstruction(opcode = 21, immLength = 1, register = 1), 36, 0x05,
+ 0xDC.toByte()),
+ program)
+ // TODO: add back disassembling test check after we update the apf_disassembler
+ // assertContentEquals(arrayOf(" 0: alloc"), ApfJniUtils.disassembleApf(program))
gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
gen.addTransmit()
@@ -100,63 +227,134 @@ class ApfV5Test {
// TODO: add back disassembling test check after we update the apf_disassembler
// assertContentEquals(arrayOf(" 0: trans"), ApfJniUtils.disassembleApf(program))
- // TODO: add back when support write opcode
-// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-// gen.addWrite(0x01, 1)
-// gen.addWrite(0x0102, 2)
-// gen.addWrite(0x01020304, 4)
-// program = gen.generate()
-// assertContentEquals(byteArrayOf(
-// encodeInstruction(24, 1, 0), 0x01,
-// encodeInstruction(24, 2, 0), 0x01, 0x02,
-// encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04
-// ), program)
-// assertContentEquals(arrayOf(
-// " 0: write 0x01",
-// " 2: write 0x0102",
-// " 5: write 0x01020304"), ApfJniUtils.disassembleApf(program))
-//
-// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-// gen.addWrite(ApfGenerator.Register.R0, 1)
-// gen.addWrite(ApfGenerator.Register.R0, 2)
-// gen.addWrite(ApfGenerator.Register.R0, 4)
-// program = gen.generate()
-// assertContentEquals(byteArrayOf(
-// encodeInstruction(21, 1, 0), 38,
-// encodeInstruction(21, 1, 0), 39,
-// encodeInstruction(21, 1, 0), 40
-// ), program)
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ val largeByteArray = ByteArray(256) { 0x01 }
+ gen.addData(largeByteArray)
+ program = gen.generate()
+ // encoding DATA opcode: opcode=14(JMP), R=1
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(opcode = 14, immLength = 2, register = 1), 0x01, 0x00) +
+ largeByteArray, program)
+
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addWriteU8(0x01)
+ gen.addWriteU16(0x0102)
+ gen.addWriteU32(0x01020304)
+ gen.addWriteU8(0x00)
+ gen.addWriteU8(0x80)
+ gen.addWriteU16(0x0000)
+ gen.addWriteU16(0x8000)
+ gen.addWriteU32(0x00000000)
+ gen.addWriteU32(0x80000000)
+ program = gen.generate()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(24, 1, 0), 0x01,
+ encodeInstruction(24, 2, 0), 0x01, 0x02,
+ encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04,
+ encodeInstruction(24, 1, 0), 0x00,
+ encodeInstruction(24, 1, 0), 0x80.toByte(),
+ encodeInstruction(24, 2, 0), 0x00, 0x00,
+ encodeInstruction(24, 2, 0), 0x80.toByte(), 0x00,
+ encodeInstruction(24, 4, 0), 0x00, 0x00, 0x00, 0x00,
+ encodeInstruction(24, 4, 0), 0x80.toByte(), 0x00, 0x00,
+ 0x00), program)
+ assertContentEquals(arrayOf(
+ " 0: write 0x01",
+ " 2: write 0x0102",
+ " 5: write 0x01020304",
+ " 10: write 0x00",
+ " 12: write 0x80",
+ " 14: write 0x0000",
+ " 17: write 0x8000",
+ " 20: write 0x00000000",
+ " 25: write 0x80000000"),
+ ApfJniUtils.disassembleApf(program))
+
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addWriteU8(R0)
+ gen.addWriteU16(R0)
+ gen.addWriteU32(R0)
+ gen.addWriteU8(R1)
+ gen.addWriteU16(R1)
+ gen.addWriteU32(R1)
+ program = gen.generate()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(21, 1, 0), 38,
+ encodeInstruction(21, 1, 0), 39,
+ encodeInstruction(21, 1, 0), 40,
+ encodeInstruction(21, 1, 1), 38,
+ encodeInstruction(21, 1, 1), 39,
+ encodeInstruction(21, 1, 1), 40
+ ), program)
+ // TODO: add back disassembling test check after we update the apf_disassembler
// assertContentEquals(arrayOf(
-// " 0: write r0, 1",
-// " 2: write r0, 2",
-// " 4: write r0, 4"), ApfJniUtils.disassembleApf(program))
-
- // TODO: add back when we properly support copy opcode
-// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-// gen.addDataCopy(1, 5)
-// gen.addPacketCopy(1000, 255)
-// program = gen.generate()
-// assertContentEquals(byteArrayOf(
-// encodeInstruction(25, 1, 1), 1, 5,
-// encodeInstruction(25, 2, 0),
-// 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(),
-// ), program)
+// " 0: ewrite1 r0",
+// " 2: ewrite2 r0",
+// " 4: ewrite4 r0",
+// " 6: ewrite1 r1",
+// " 8: ewrite2 r1",
+// " 10: ewrite4 r1"), ApfJniUtils.disassembleApf(program))
+
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addDataCopy(0, 10)
+ gen.addDataCopy(1, 5)
+ gen.addPacketCopy(1000, 255)
+ program = gen.generate()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(25, 0, 1), 10,
+ encodeInstruction(25, 1, 1), 1, 5,
+ encodeInstruction(25, 2, 0),
+ 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(),
+ ), program)
+ // TODO: add back disassembling test check after we update the apf_disassembler
// assertContentEquals(arrayOf(
-// " 0: dcopy 1, 5",
+// " 0: dcopy 0, 5",
// " 3: pcopy 1000, 255"), ApfJniUtils.disassembleApf(program))
-//
-// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-// gen.addDataCopy(ApfGenerator.Register.R1, 0, 5)
-// gen.addPacketCopy(ApfGenerator.Register.R0, 1000, 255)
-// program = gen.generate()
-// assertContentEquals(byteArrayOf(
-// encodeInstruction(21, 1, 1), 42, 0, 5,
-// encodeInstruction(21, 2, 0),
-// 0, 41, 0x03.toByte(), 0xe8.toByte(), 0xff.toByte()
-// ), program)
+
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addPacketCopyFromR0LenR1()
+ gen.addPacketCopyFromR0(5)
+ gen.addDataCopyFromR0LenR1()
+ gen.addDataCopyFromR0(5)
+ program = gen.generate()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(21, 1, 1), 41,
+ encodeInstruction(21, 1, 0), 41, 5,
+ encodeInstruction(21, 1, 1), 42,
+ encodeInstruction(21, 1, 0), 42, 5,
+ ), program)
+ // TODO: add back the following test case when implementing EPKTCOPY, EDATACOPY opcodes.
// assertContentEquals(arrayOf(
// " 0: dcopy [r1+0], 5",
// " 4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(program))
+
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfGenerator.DROP_LABEL)
+ program = gen.generate()
+ assertContentEquals(
+ byteArrayOf(encodeInstruction(opcode = 20, immLength = 1, register = 1),
+ 1, 1, 'a'.code.toByte()), program)
+
+ val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0)
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfGenerator.DROP_LABEL)
+ gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfGenerator.DROP_LABEL)
+ program = gen.generate()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(21, 1, 0), 43, 11, 0x0c.toByte(),
+ ) + qnames + byteArrayOf(
+ encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(),
+ ) + qnames, program)
+
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfGenerator.DROP_LABEL)
+ gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfGenerator.DROP_LABEL)
+ program = gen.generate()
+ assertContentEquals(byteArrayOf(
+ encodeInstruction(21, 1, 0), 44, 10,
+ ) + qnames + byteArrayOf(
+ encodeInstruction(21, 1, 1), 44, 1,
+ ) + qnames, program)
}
private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte {
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
index b5e806f9..32cf4647 100644
--- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
+++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -220,4 +220,168 @@ class Dhcp6PacketTest {
assertEquals(423, packet.prefixDelegation.minimalPreferredLifetime)
assertEquals(43200, packet.prefixDelegation.minimalValidLifetime)
}
+
+ @Test
+ fun testStatusCodeOptionWithStatusMessage() {
+ val replyHex =
+ // Reply, Transaction ID
+ "07000A47" +
+ // server identifier option(option_len=10)
+ "0002000A0003000186C9B26AED4D" +
+ // client identifier option(option_len=12)
+ "0001000C0003001B02FBBAFFFEB7BC71" +
+ // SOL_MAX_RT (don't support this option yet)
+ "005200040000003c" +
+ // Rapid Commit
+ "000e0000" +
+ // DNS recursive server (don't support this opton yet)
+ "00170010fdfd9ed6795000000000000000000001" +
+ // IA_PD option (t1=t2=0, empty prefix)
+ "0019000c000000000000000000000000" +
+ // Status code option: status code=NoPrefixAvail
+ "000d00150006" +
+ // Status code option: status message="no prefix available"
+ "6e6f2070726566697820617661696c61626c65"
+ val bytes = HexDump.hexStringToByteArray(replyHex)
+ val packet = Dhcp6Packet.decode(bytes, bytes.size)
+ assertTrue(packet is Dhcp6ReplyPacket)
+ assertEquals(0, packet.mPrefixDelegation.iaid)
+ assertEquals(0, packet.mPrefixDelegation.t1)
+ assertEquals(0, packet.mPrefixDelegation.t2)
+ assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode)
+ }
+
+ @Test
+ fun testStatusCodeOptionWithoutStatusMessage() {
+ val replyHex =
+ // Reply, Transaction ID
+ "07000A47" +
+ // server identifier option(option_len=10)
+ "0002000A0003000186C9B26AED4D" +
+ // client identifier option(option_len=12)
+ "0001000C0003001B02FBBAFFFEB7BC71" +
+ // SOL_MAX_RT (don't support this option yet)
+ "005200040000003c" +
+ // Rapid Commit
+ "000e0000" +
+ // DNS recursive server (don't support this opton yet)
+ "00170010fdfd9ed6795000000000000000000001" +
+ // IA_PD option (t1=t2=0, empty prefix)
+ "0019000c000000000000000000000000" +
+ // Status code option: status code=NoPrefixAvail
+ "000d00020006"
+ val bytes = HexDump.hexStringToByteArray(replyHex)
+ val packet = Dhcp6Packet.decode(bytes, bytes.size)
+ assertTrue(packet is Dhcp6ReplyPacket)
+ assertEquals(0, packet.mPrefixDelegation.iaid)
+ assertEquals(0, packet.mPrefixDelegation.t1)
+ assertEquals(0, packet.mPrefixDelegation.t2)
+ assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode)
+ }
+
+ @Test
+ fun testStatusCodeOptionInIaPdWithStatusMessage() {
+ val replyHex =
+ // Reply, Transaction ID
+ "07000A47" +
+ // server identifier option(option_len=10)
+ "0002000A0003000186C9B26AED4D" +
+ // client identifier option(option_len=12)
+ "0001000C0003001B02FBBAFFFEB7BC71" +
+ // SOL_MAX_RT (don't support this option yet)
+ "005200040000003c" +
+ // Rapid Commit
+ "000e0000" +
+ // DNS recursive server (don't support this opton yet)
+ "00170010fdfd9ed6795000000000000000000001" +
+ // IA_PD option (t1=t2=0, status code=NoPrefixAvail,
+ // status message="no prefix available")
+ "00190025000000000000000000000000000d00150006" +
+ "6e6f2070726566697820617661696c61626c65"
+ val bytes = HexDump.hexStringToByteArray(replyHex)
+ val packet = Dhcp6Packet.decode(bytes, bytes.size)
+ assertTrue(packet is Dhcp6ReplyPacket)
+ assertEquals(0, packet.mPrefixDelegation.iaid)
+ assertEquals(0, packet.mPrefixDelegation.t1)
+ assertEquals(0, packet.mPrefixDelegation.t2)
+ assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode)
+ }
+
+ @Test
+ fun testStatusCodeOptionInIaPdWithoutStatusMessage() {
+ val replyHex =
+ // Reply, Transaction ID
+ "07000A47" +
+ // server identifier option(option_len=10)
+ "0002000A0003000186C9B26AED4D" +
+ // client identifier option(option_len=12)
+ "0001000C0003001B02FBBAFFFEB7BC71" +
+ // SOL_MAX_RT (don't support this option yet)
+ "005200040000003c" +
+ // Rapid Commit
+ "000e0000" +
+ // DNS recursive server (don't support this opton yet)
+ "00170010fdfd9ed6795000000000000000000001" +
+ // IA_PD option (t1=t2=0, status code=NoPrefixAvail)
+ "00190012000000000000000000000000000d00020006"
+ val bytes = HexDump.hexStringToByteArray(replyHex)
+ val packet = Dhcp6Packet.decode(bytes, bytes.size)
+ assertTrue(packet is Dhcp6ReplyPacket)
+ assertEquals(0, packet.mPrefixDelegation.iaid)
+ assertEquals(0, packet.mPrefixDelegation.t1)
+ assertEquals(0, packet.mPrefixDelegation.t2)
+ assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode)
+ }
+
+ @Test
+ fun testStatusCodeOptionWithTruncatedStatusMessage() {
+ val replyHex =
+ // Reply, Transaction ID
+ "07000A47" +
+ // server identifier option(option_len=10)
+ "0002000A0003000186C9B26AED4D" +
+ // client identifier option(option_len=12)
+ "0001000C0003001B02FBBAFFFEB7BC71" +
+ // SOL_MAX_RT (don't support this option yet)
+ "005200040000003c" +
+ // Rapid Commit
+ "000e0000" +
+ // DNS recursive server (don't support this opton yet)
+ "00170010fdfd9ed6795000000000000000000001" +
+ // Status code option: len=21, status code=NoPrefixAvail
+ "000d00150006" +
+ // Status code option: truncated status message="no prefix available"
+ "6e6f2070726566697820617661696c6162"
+ val bytes = HexDump.hexStringToByteArray(replyHex)
+ assertThrows(Dhcp6Packet.ParseException::class.java) {
+ Dhcp6Packet.decode(bytes, bytes.size)
+ }
+ }
+
+ @Test
+ fun testStatusCodeOptionInIaPdWithTruncatedStatusMessage() {
+ val replyHex =
+ // Reply, Transaction ID
+ "07000A47" +
+ // server identifier option(option_len=10)
+ "0002000A0003000186C9B26AED4D" +
+ // client identifier option(option_len=12)
+ "0001000C0003001B02FBBAFFFEB7BC71" +
+ // SOL_MAX_RT (don't support this option yet)
+ "005200040000003c" +
+ // Rapid Commit
+ "000e0000" +
+ // DNS recursive server (don't support this opton yet)
+ "00170010fdfd9ed6795000000000000000000001" +
+ // IA_PD option (t1=t2=0, empty prefix)
+ "00190025000000000000000000000000" +
+ // Status code option: len=21, status code=NoPrefixAvail
+ "000d00150006" +
+ // truncated status message="no prefix available")
+ "6e6f2070726566697820617661696c6162"
+ val bytes = HexDump.hexStringToByteArray(replyHex)
+ assertThrows(Dhcp6Packet.ParseException::class.java) {
+ Dhcp6Packet.decode(bytes, bytes.size)
+ }
+ }
}
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
index 6471f3ae..4d57df5e 100644
--- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
+++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -56,7 +56,6 @@ import com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED
import com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE
import com.android.net.module.util.netlink.StructNdMsg.NUD_STALE
import com.android.networkstack.metrics.IpReachabilityMonitorMetrics
-import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION
import com.android.testutils.makeNewNeighMessage
import com.android.testutils.waitForIdle
import java.io.FileDescriptor
@@ -259,8 +258,6 @@ class IpReachabilityMonitorTest {
}.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any())
doReturn(mIpReachabilityMonitorMetrics)
.`when`(dependencies).getIpReachabilityMonitorMetrics()
- doReturn(true).`when`(dependencies).isFeatureNotChickenedOut(any(),
- eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION))
val monitorFuture = CompletableFuture<IpReachabilityMonitor>()
// IpReachabilityMonitor needs to be started from the handler thread
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 77e3a129..43bee559 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -19,6 +19,7 @@ package com.android.server.connectivity;
import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.net.DnsResolver.TYPE_A;
import static android.net.DnsResolver.TYPE_AAAA;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
@@ -29,6 +30,7 @@ import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
+import static android.net.InetAddresses.parseNumericAddress;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -64,6 +66,7 @@ import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USE
import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT;
import static com.android.networkstack.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION;
import static com.android.networkstack.util.NetworkStackUtils.REEVALUATE_WHEN_RESUME;
+import static com.android.server.connectivity.NetworkMonitor.CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS;
import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS;
import static com.android.server.connectivity.NetworkMonitor.extractCharset;
@@ -123,6 +126,7 @@ import android.net.Network;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkTestResultParcelable;
+import android.net.PrivateDnsConfigParcel;
import android.net.Uri;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
@@ -168,6 +172,7 @@ import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.metrics.DataStallDetectionStats;
import com.android.networkstack.metrics.DataStallStatsUtils;
import com.android.networkstack.netlink.TcpSocketTracker;
+import com.android.networkstack.util.NetworkStackUtils;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
import com.android.server.connectivity.nano.CellularData;
import com.android.server.connectivity.nano.DnsEvent;
@@ -214,8 +219,12 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import javax.net.ssl.SSLHandshakeException;
@@ -366,11 +375,11 @@ public class NetworkMonitorTest {
class DnsEntry {
final String mHostname;
final int mType;
- final List<InetAddress> mAddresses;
- DnsEntry(String host, int type, List<InetAddress> addr) {
+ final AddressSupplier mAddressesSupplier;
+ DnsEntry(String host, int type, AddressSupplier addr) {
mHostname = host;
mType = type;
- mAddresses = addr;
+ mAddressesSupplier = addr;
}
// Full match or partial match that target host contains the entry hostname to support
// random private dns probe hostname.
@@ -378,6 +387,21 @@ public class NetworkMonitorTest {
return hostname.endsWith(mHostname) && type == mType;
}
}
+ interface AddressSupplier {
+ List<InetAddress> get() throws DnsResolver.DnsException;
+ }
+
+ class InstantAddressSupplier implements AddressSupplier {
+ private final List<InetAddress> mAddresses;
+ InstantAddressSupplier(List<InetAddress> addresses) {
+ mAddresses = addresses;
+ }
+ @Override
+ public List<InetAddress> get() {
+ return mAddresses;
+ }
+ }
+
private final ArrayList<DnsEntry> mAnswers = new ArrayList<DnsEntry>();
private boolean mNonBypassPrivateDnsWorking = true;
@@ -387,40 +411,76 @@ public class NetworkMonitorTest {
}
/** Clears all DNS entries. */
- private synchronized void clearAll() {
- mAnswers.clear();
+ private void clearAll() {
+ synchronized (mAnswers) {
+ mAnswers.clear();
+ }
}
/** Returns the answer for a given name and type on the given mock network. */
- private synchronized List<InetAddress> getAnswer(Object mock, String hostname, int type) {
- if (mock == mNetwork && !mNonBypassPrivateDnsWorking) {
- return null;
+ private CompletableFuture<List<InetAddress>> getAnswer(Network mockNetwork, String hostname,
+ int type) {
+ if (mockNetwork == mNetwork && !mNonBypassPrivateDnsWorking) {
+ return CompletableFuture.completedFuture(null);
}
- return mAnswers.stream().filter(e -> e.matches(hostname, type))
- .map(answer -> answer.mAddresses).findFirst().orElse(null);
+ final AddressSupplier answerSupplier;
+
+ synchronized (mAnswers) {
+ answerSupplier = mAnswers.stream()
+ .filter(e -> e.matches(hostname, type))
+ .map(answer -> answer.mAddressesSupplier).findFirst().orElse(null);
+ }
+ if (answerSupplier == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ if (answerSupplier instanceof InstantAddressSupplier) {
+ // Save latency waiting for a query thread if the answer is hardcoded.
+ return CompletableFuture.completedFuture(
+ ((InstantAddressSupplier) answerSupplier).get());
+ }
+ final CompletableFuture<List<InetAddress>> answerFuture = new CompletableFuture<>();
+ new Thread(() -> {
+ try {
+ answerFuture.complete(answerSupplier.get());
+ } catch (DnsResolver.DnsException e) {
+ answerFuture.completeExceptionally(e);
+ }
+ }).start();
+ return answerFuture;
}
/** Sets the answer for a given name and type. */
- private synchronized void setAnswer(String hostname, String[] answer, int type)
- throws UnknownHostException {
- DnsEntry record = new DnsEntry(hostname, type, generateAnswer(answer));
- // Remove the existing one.
- mAnswers.removeIf(entry -> entry.matches(hostname, type));
- // Add or replace a new record.
- mAnswers.add(record);
+ private void setAnswer(String hostname, String[] answer, int type) {
+ setAnswer(hostname, new InstantAddressSupplier(generateAnswer(answer)), type);
+ }
+
+ private void setAnswer(String hostname, AddressSupplier answerSupplier, int type) {
+ DnsEntry record = new DnsEntry(hostname, type, answerSupplier);
+ synchronized (mAnswers) {
+ // Remove the existing one.
+ mAnswers.removeIf(entry -> entry.matches(hostname, type));
+ // Add or replace a new record.
+ mAnswers.add(record);
+ }
}
private List<InetAddress> generateAnswer(String[] answer) {
if (answer == null) return new ArrayList<>();
- return Arrays.stream(answer).map(addr -> InetAddress.parseNumericAddress(addr))
- .collect(toList());
+ return Arrays.stream(answer).map(InetAddresses::parseNumericAddress).collect(toList());
}
/** Simulates a getAllByName call for the specified name on the specified mock network. */
- private InetAddress[] getAllByName(Object mock, String hostname)
+ private InetAddress[] getAllByName(Network mockNetwork, String hostname)
throws UnknownHostException {
- List<InetAddress> answer = queryAllTypes(mock, hostname);
+ final List<InetAddress> answer;
+ try {
+ answer = queryAllTypes(mockNetwork, hostname).get(
+ HANDLER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ throw new AssertionError("No mock DNS reply within timeout", e);
+ }
if (answer == null || answer.size() == 0) {
throw new UnknownHostException(hostname);
}
@@ -428,57 +488,76 @@ public class NetworkMonitorTest {
}
// Regardless of the type, depends on what the responses contained in the network.
- private List<InetAddress> queryAllTypes(Object mock, String hostname) {
- List<InetAddress> answer = new ArrayList<>();
- addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_A));
- addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_AAAA));
- return answer;
- }
-
- private void addAllIfNotNull(List<InetAddress> list, List<InetAddress> c) {
- if (c != null) {
- list.addAll(c);
+ private CompletableFuture<List<InetAddress>> queryAllTypes(
+ Network mockNetwork, String hostname) {
+ if (mockNetwork == mNetwork && !mNonBypassPrivateDnsWorking) {
+ return CompletableFuture.completedFuture(null);
}
+
+ final CompletableFuture<List<InetAddress>> aFuture =
+ getAnswer(mockNetwork, hostname, TYPE_A)
+ .exceptionally(e -> Collections.emptyList());
+ final CompletableFuture<List<InetAddress>> aaaaFuture =
+ getAnswer(mockNetwork, hostname, TYPE_AAAA)
+ .exceptionally(e -> Collections.emptyList());
+
+ final CompletableFuture<List<InetAddress>> combinedFuture = new CompletableFuture<>();
+ aFuture.thenAcceptBoth(aaaaFuture, (res1, res2) -> {
+ final List<InetAddress> answer = new ArrayList<>();
+ if (res1 != null) answer.addAll(res1);
+ if (res2 != null) answer.addAll(res2);
+ combinedFuture.complete(answer);
+ });
+ return combinedFuture;
}
/** Starts mocking DNS queries. */
private void startMocking() throws UnknownHostException {
// Queries on mNetwork using getAllByName.
doAnswer(invocation -> {
- return getAllByName(invocation.getMock(), invocation.getArgument(0));
+ return getAllByName((Network) invocation.getMock(), invocation.getArgument(0));
}).when(mNetwork).getAllByName(any());
// Queries on mCleartextDnsNetwork using DnsResolver#query.
doAnswer(invocation -> {
- return mockQuery(invocation, 1 /* posHostname */, 3 /* posExecutor */,
- 5 /* posCallback */, -1 /* posType */);
+ return mockQuery(invocation, 0 /* posNetwork */, 1 /* posHostname */,
+ 3 /* posExecutor */, 5 /* posCallback */, -1 /* posType */);
}).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
// Queries on mCleartextDnsNetwork using DnsResolver#query with QueryType.
doAnswer(invocation -> {
- return mockQuery(invocation, 1 /* posHostname */, 4 /* posExecutor */,
- 6 /* posCallback */, 2 /* posType */);
+ return mockQuery(invocation, 0 /* posNetwork */, 1 /* posHostname */,
+ 4 /* posExecutor */, 6 /* posCallback */, 2 /* posType */);
}).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any());
}
// Mocking queries on DnsResolver#query.
- private Answer mockQuery(InvocationOnMock invocation, int posHostname, int posExecutor,
- int posCallback, int posType) {
+ private Answer mockQuery(InvocationOnMock invocation, int posNetwork, int posHostname,
+ int posExecutor, int posCallback, int posType) {
String hostname = (String) invocation.getArgument(posHostname);
Executor executor = (Executor) invocation.getArgument(posExecutor);
DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(posCallback);
- List<InetAddress> answer;
-
- answer = posType != -1
- ? getAnswer(invocation.getMock(), hostname, invocation.getArgument(posType)) :
- queryAllTypes(invocation.getMock(), hostname);
-
- if (answer != null && answer.size() > 0) {
- new Handler(Looper.getMainLooper()).post(() -> {
- executor.execute(() -> callback.onAnswer(answer, 0));
- });
- }
- // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
+ Network network = invocation.getArgument(posNetwork);
+
+ final CompletableFuture<List<InetAddress>> answerFuture = posType != -1
+ ? getAnswer(network, hostname, invocation.getArgument(posType))
+ : queryAllTypes(network, hostname);
+
+ answerFuture.whenComplete((answer, exception) -> {
+ new Handler(Looper.getMainLooper()).post(() -> executor.execute(() -> {
+ if (exception != null) {
+ if (!(exception instanceof DnsResolver.DnsException)) {
+ throw new AssertionError("Test error building DNS response", exception);
+ }
+ callback.onError((DnsResolver.DnsException) exception);
+ return;
+ }
+ if (answer != null && answer.size() > 0) {
+ callback.onAnswer(answer, 0);
+ }
+ }));
+ });
+ // If the future does not complete or has no answer do nothing. The timeout should fire.
return null;
}
}
@@ -528,6 +607,8 @@ public class NetworkMonitorTest {
// it will fail the test because of timeout expired for querying AAAA and A sequentially.
doReturn(200).when(mResources)
.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout));
+ doReturn(200).when(mDependencies).getDeviceConfigPropertyInt(
+ eq(NAMESPACE_CONNECTIVITY), eq(CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS), anyInt());
doAnswer((invocation) -> {
URL url = invocation.getArgument(0);
@@ -1239,7 +1320,7 @@ public class NetworkMonitorTest {
setPortal302(mHttpConnection);
final String httpHost = new URL(TEST_HTTP_URL).getHost();
mFakeDns.setAnswer(httpHost, new String[] { "2001:db8::123" }, TYPE_AAAA);
- final InetAddress parsedPrivateAddr = InetAddresses.parseNumericAddress(privateAddr);
+ final InetAddress parsedPrivateAddr = parseNumericAddress(privateAddr);
mFakeDns.setAnswer(httpHost, new String[] { privateAddr },
(parsedPrivateAddr instanceof Inet6Address) ? TYPE_AAAA : TYPE_A);
}
@@ -2199,8 +2280,7 @@ public class NetworkMonitorTest {
NETWORK_VALIDATION_RESULT_VALID, 0 /* probesSucceeded */));
}
- @Test
- public void testPrivateDnsSuccess() throws Exception {
+ private void runPrivateDnsSuccessTest() throws Exception {
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 204);
@@ -2232,7 +2312,20 @@ public class NetworkMonitorTest {
}
@Test
- public void testProbeStatusChanged() throws Exception {
+ public void testPrivateDnsSuccess_SyncDns() throws Exception {
+ doReturn(false).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ runPrivateDnsSuccessTest();
+ }
+
+ @Test
+ public void testPrivateDnsSuccess_AsyncDns() throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ runPrivateDnsSuccessTest();
+ }
+
+ private void runProbeStatusChangedTest() throws Exception {
// Set no record in FakeDns and expect validation to fail.
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 204);
@@ -2255,7 +2348,20 @@ public class NetworkMonitorTest {
}
@Test
- public void testPrivateDnsResolutionRetryUpdate() throws Exception {
+ public void testProbeStatusChanged_SyncDns() throws Exception {
+ doReturn(false).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ runProbeStatusChangedTest();
+ }
+
+ @Test
+ public void testProbeStatusChanged_AsyncDns() throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ runProbeStatusChangedTest();
+ }
+
+ private void runPrivateDnsResolutionRetryUpdateTest() throws Exception {
// Set no record in FakeDns and expect validation to fail.
setStatus(mHttpsConnection, 204);
setStatus(mHttpConnection, 204);
@@ -2300,6 +2406,197 @@ public class NetworkMonitorTest {
}
@Test
+ public void testPrivateDnsResolutionRetryUpdate_SyncDns() throws Exception {
+ doReturn(false).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ runPrivateDnsResolutionRetryUpdateTest();
+ }
+
+ @Test
+ public void testPrivateDnsResolutionRetryUpdate_AsyncDns() throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ runPrivateDnsResolutionRetryUpdateTest();
+ }
+
+ @Test
+ public void testAsyncPrivateDnsResolution_PartialTimeout() throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+
+ WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+
+ // Only provide AAAA answer
+ mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA);
+
+ notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
+ verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+
+ final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel();
+ expectedConfig.hostname = "dns.google";
+ expectedConfig.ips = new String[] {"2001:db8::1"};
+ expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+ verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig);
+ }
+
+ @Test
+ public void testAsyncPrivateDnsResolution_PartialFailure() throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+
+ WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+
+ // A succeeds, AAAA fails
+ mFakeDns.setAnswer("dns.google", new String[]{"192.0.2.123"}, TYPE_A);
+ mFakeDns.setAnswer("dns.google", () -> {
+ // DnsResolver.DnsException constructor is T+, so use a mock instead
+ throw mock(DnsResolver.DnsException.class);
+ }, TYPE_AAAA);
+
+ notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
+ verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+
+ final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel();
+ expectedConfig.hostname = "dns.google";
+ expectedConfig.ips = new String[] {"192.0.2.123"};
+ expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+ verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig);
+ }
+
+ @Test
+ public void testAsyncPrivateDnsResolution_AQuerySucceedsFirst_PrioritizeAAAA()
+ throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+
+ WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+
+ final ConditionVariable v4Queried = new ConditionVariable();
+ mFakeDns.setAnswer("dns.google", () -> {
+ v4Queried.open();
+ return List.of(parseNumericAddress("192.0.2.123"));
+ }, TYPE_A);
+ mFakeDns.setAnswer("dns.google", () -> {
+ // Make sure the v6 query processing is a bit slower than the v6 one. The small delay
+ // below still does not guarantee that the v4 query will complete first, but it should
+ // the large majority of the time, which should be enough to test it. Even if it does
+ // not, the test should pass.
+ v4Queried.block(HANDLER_TIMEOUT_MS);
+ SystemClock.sleep(10L);
+ return List.of(parseNumericAddress("2001:db8::1"), parseNumericAddress("2001:db8::2"));
+ }, TYPE_AAAA);
+
+ notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
+ verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+
+ final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel();
+ expectedConfig.hostname = "dns.google";
+ // The IPv6 addresses are still first
+ expectedConfig.ips = new String[] {"2001:db8::1", "2001:db8::2", "192.0.2.123"};
+ expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+ verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig);
+ }
+
+ @Test
+ public void testAsyncPrivateDnsResolution_ConfigChange_RestartsWithNewConfig()
+ throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+
+ WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("v1.google", new InetAddress[0]));
+
+ final ConditionVariable blockReplies = new ConditionVariable();
+ final CountDownLatch queriedLatch = new CountDownLatch(2);
+ mFakeDns.setAnswer("v1.google", () -> {
+ queriedLatch.countDown();
+ blockReplies.block(HANDLER_TIMEOUT_MS);
+ return List.of(parseNumericAddress("192.0.2.123"));
+ }, TYPE_A);
+ mFakeDns.setAnswer("v1.google", () -> {
+ queriedLatch.countDown();
+ blockReplies.block(HANDLER_TIMEOUT_MS);
+ return List.of(parseNumericAddress("2001:db8::1"));
+ }, TYPE_AAAA);
+
+ notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
+
+ queriedLatch.await(HANDLER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ // Send config update while DNS queries are in flight
+ mFakeDns.setAnswer("v2.google", new String[] { "192.0.2.124" }, TYPE_A);
+ mFakeDns.setAnswer("v2.google", new String[] { "2001:db8::2" }, TYPE_AAAA);
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("v2.google", new InetAddress[0]));
+
+ // Let the original queries finish. Once DNS queries finish results are posted to the
+ // handler, so they will be processed on the handler after the DNS settings change.
+ blockReplies.open();
+
+ // Expect only callbacks for the 2nd configuration
+ verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+
+ final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel();
+ expectedConfig.hostname = "v2.google";
+ expectedConfig.ips = new String[] {"2001:db8::2", "192.0.2.124"};
+ expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
+ verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig);
+ }
+
+ @Test
+ public void testAsyncPrivateDnsResolution_TurnOffStrictMode_SkipsDnsValidation()
+ throws Exception {
+ doReturn(true).when(mDependencies).isFeatureEnabled(
+ any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION));
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+
+ WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("v1.google", new InetAddress[0]));
+
+ final ConditionVariable blockReplies = new ConditionVariable();
+ final CountDownLatch queriedLatch = new CountDownLatch(2);
+ mFakeDns.setAnswer("v1.google", () -> {
+ queriedLatch.countDown();
+ blockReplies.block(HANDLER_TIMEOUT_MS);
+ return List.of(parseNumericAddress("192.0.2.123"));
+ }, TYPE_A);
+ mFakeDns.setAnswer("v1.google", () -> {
+ queriedLatch.countDown();
+ blockReplies.block(HANDLER_TIMEOUT_MS);
+ return List.of(parseNumericAddress("2001:db8::1"));
+ }, TYPE_AAAA);
+
+ notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
+
+ queriedLatch.await(HANDLER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ // Send config update while DNS queries are in flight
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true /* useTls */));
+
+ // Let the original queries finish. Once DNS queries finish results are posted to the
+ // handler, so they will be processed on the handler after the DNS settings change.
+ blockReplies.open();
+
+ verifyNetworkTestedValidFromHttps(1 /* interactions */);
+ verify(mCallbacks, never()).notifyPrivateDnsConfigResolved(any());
+ }
+
+ @Test
public void testReevaluationInterval_networkResume() throws Exception {
// Setup nothing and expect validation to fail.
doReturn(true).when(mDependencies).isFeatureEnabled(any(), eq(REEVALUATE_WHEN_RESUME));
@@ -2725,8 +3022,8 @@ public class NetworkMonitorTest {
} catch (UnknownHostException e) {
}
- mFakeDns.setAnswer("www.android.com", null, TYPE_A);
- mFakeDns.setAnswer("www.android.com", null, TYPE_AAAA);
+ mFakeDns.setAnswer("www.android.com", (String[]) null, TYPE_A);
+ mFakeDns.setAnswer("www.android.com", (String[]) null, TYPE_AAAA);
try {
wnm.sendDnsProbeWithTimeout("www.android.com", shortTimeoutMs);
fail("DNS query timed out, expected UnknownHostException");